Exemple #1
0
class Template:
    __metaclass__ = PoolMeta
    __name__ = 'product.template'
    depreciable = fields.Boolean('Depreciable',
                                 states={
                                     'readonly': ~Eval('active', True),
                                     'invisible': Eval('type', '') != 'assets',
                                 },
                                 depends=['active', 'type'])
    account_depreciation = fields.MultiValue(
        fields.Many2One(
            'account.account',
            'Account Depreciation',
            domain=[
                ('kind', '=', 'other'),
                ('company', '=', Eval('context', {}).get('company', -1)),
            ],
            states={
                'readonly':
                ~Eval('active', True),
                'required':
                ~Eval('accounts_category') & Eval('depreciable'),
                'invisible': (~Eval('depreciable')
                              | (Eval('type', '') != 'assets')
                              | ~Eval('context', {}).get('company')
                              | Eval('accounts_category')),
            },
            depends=['active', 'depreciable', 'type', 'accounts_category']))
    account_asset = fields.MultiValue(
        fields.Many2One(
            'account.account',
            'Account Asset',
            domain=[
                ('kind', '=', 'expense'),
                ('company', '=', Eval('context', {}).get('company', -1)),
            ],
            states={
                'readonly':
                ~Eval('active', True),
                'required':
                ~Eval('accounts_category') & Eval('depreciable'),
                'invisible': (~Eval('depreciable')
                              | (Eval('type', '') != 'assets')
                              | ~Eval('context', {}).get('company')
                              | Eval('accounts_category')),
            },
            depends=['active', 'depreciable', 'type', 'accounts_category']))
    depreciation_duration = fields.Integer(
        "Depreciation Duration",
        states={
            'readonly': ~Eval('active', True),
            'invisible': (~Eval('depreciable')
                          | (Eval('type', '') != 'assets')),
        },
        depends=['active', 'depreciable', 'type'],
        help='In months')

    @classmethod
    def multivalue_model(cls, field):
        pool = Pool()
        if field in {'account_depreciation', 'account_asset'}:
            return pool.get('product.template.account')
        return super(Template, cls).multivalue_model(field)

    @property
    @account_used('account_depreciation')
    def account_depreciation_used(self):
        pass

    @property
    @account_used('account_asset')
    def account_asset_used(self):
        pass
Exemple #2
0
class _Action_Line:
    __slots__ = ()
    _states = {
        'readonly': ((Eval('complaint_state') != 'draft')
                     | Bool(Eval('_parent_action.result', True))),
    }

    action = fields.Many2One('sale.complaint.action',
                             'Action',
                             ondelete='CASCADE',
                             select=True,
                             required=True)
    quantity = fields.Float("Quantity", digits='unit', states=_states)
    unit = fields.Function(fields.Many2One('product.uom', "Unit"),
                           'on_change_with_unit')
    unit_price = Monetary("Unit Price",
                          currency='currency',
                          digits=price_digits,
                          states=_states,
                          help='Leave empty for the same price.')

    amount = fields.Function(
        Monetary("Amount", currency='currency', digits='currency'),
        'on_change_with_amount')
    currency = fields.Function(
        fields.Many2One('currency.currency', "Currency"),
        'on_change_with_currency')

    complaint_state = fields.Function(
        fields.Selection('get_complaint_states', "Complaint State"),
        'on_change_with_complaint_state')
    complaint_origin_id = fields.Function(
        fields.Integer("Complaint Origin ID"),
        'on_change_with_complaint_origin_id')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__access__.add('action')

    def on_change_with_unit(self, name=None):
        raise NotImplementedError

    @fields.depends('currency', methods=['get_quantity', 'get_unit_price'])
    def on_change_with_amount(self, name=None):
        quantity = self.get_quantity() or 0
        unit_price = self.get_unit_price() or Decimal(0)
        amount = Decimal(str(quantity)) * unit_price
        if self.currency:
            amount = self.currency.round(amount)
        return amount

    def get_quantity(self):
        raise NotImplementedError

    def get_unit_price(self):
        raise NotImplementedError

    @fields.depends('action', '_parent_action.currency')
    def on_change_with_currency(self, name=None):
        if self.action and self.action.currency:
            return self.action.currency.id

    @classmethod
    def get_complaint_states(cls):
        pool = Pool()
        Complaint = pool.get('sale.complaint')
        return Complaint.fields_get(['state'])['state']['selection']

    @fields.depends('action', '_parent_action.complaint',
                    '_parent_action._parent_complaint.state')
    def on_change_with_complaint_state(self, name=None):
        if self.action and self.action.complaint:
            return self.action.complaint.state

    @fields.depends('action', '_parent_action.complaint',
                    '_parent_action._parent_complaint.origin_id')
    def on_change_with_complaint_origin_id(self, name=None):
        if self.action and self.action.complaint:
            return self.action.complaint.origin_id
Exemple #3
0
class Party(metaclass=PoolMeta):
    __name__ = 'party.party'

    credit_amount = fields.Function(
        fields.Numeric('Credit Amount',
                       digits=(16, Eval('credit_limit_digits', 2)),
                       depends=['credit_limit_digits']), 'get_credit_amount')
    credit_limit_amount = fields.MultiValue(
        fields.Numeric('Credit Limit Amount',
                       digits=(16, Eval('credit_limit_digits', 2)),
                       depends=['credit_limit_digits']))
    credit_limit_digits = fields.Function(fields.Integer('Currency Digits'),
                                          'get_credit_limit_digits')
    credit_limit_amounts = fields.One2Many('party.party.credit_limit_amount',
                                           'party', "Credit Limit Amounts")

    @classmethod
    def default_credit_limit_amount(cls, **pattern):
        pool = Pool()
        Configuration = pool.get('account.configuration')
        config = Configuration(1)
        return config.get_multivalue('default_credit_limit_amount', **pattern)

    @classmethod
    def get_credit_amount(cls, parties, name):
        return {p.id: p.receivable for p in parties}

    @staticmethod
    def _credit_limit_to_lock():
        'Return models to lock when checking credit limit'
        return ['account.move.line']

    def check_credit_limit(self, amount, origin=None):
        '''
        Check if amount will not reach credit limit for party
        If origin is set and user is in group credit_limit then a warning will
        be raised
        '''
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        try:
            Dunning = pool.get('account.dunning')
        except KeyError:
            Dunning = None
        User = pool.get('res.user')
        Group = pool.get('res.group')
        Company = pool.get('company.company')
        Lang = pool.get('ir.lang')
        Warning = pool.get('res.user.warning')

        if self.credit_limit_amount is None:
            return

        def in_group():
            group = Group(
                ModelData.get_id('account_credit_limit', 'group_credit_limit'))
            transaction = Transaction()
            user_id = transaction.user
            if user_id == 0:
                user_id = transaction.context.get('user', user_id)
            if user_id == 0:
                return True
            user = User(user_id)
            return origin and group in user.groups

        for model in self._credit_limit_to_lock():
            Model = pool.get(model)
            Transaction().database.lock(Transaction().connection, Model._table)
        if self.credit_limit_amount < self.credit_amount + amount:
            company = Company(Transaction().context.get('company'))
            lang = Lang.get()
            limit = lang.currency(self.credit_limit_amount, company.currency)
            if not in_group():
                raise CreditLimitError(
                    gettext(
                        'account_credit_limit'
                        '.msg_party_credit_limit_amount',
                        party=self.rec_name,
                        limit=limit))
            warning_name = 'credit_limit_amount_%s' % origin
            if Warning.check(warning_name):
                raise CreditLimitWarning(
                    warning_name,
                    gettext(
                        'account_credit_limit'
                        '.msg_party_credit_limit_amount',
                        party=self.rec_name,
                        limit=limit))

        if Dunning:
            dunnings = Dunning.search([
                ('party', '=', self.id),
                ('level.credit_limit', '=', True),
                ('blocked', '!=', True),
            ])
            if dunnings:
                dunning = dunnings[0]
                if not in_group():
                    raise CreditLimitError(
                        gettext(
                            'account_credit_limit'
                            '.msg_party_credit_limit_dunning',
                            party=self.rec_name,
                            dunning=dunning.rec_name))
                warning_name = 'credit_limit_dunning_%s' % origin
                if Warning.check(warning_name):
                    raise CreditLimitWarning(
                        warning_name,
                        gettext(
                            'account_credit_limit'
                            '.msg_party_credit_limit_dunning',
                            party=self.rec_name,
                            dunning=dunning.rec_name))

    def get_credit_limit_digits(self, name):
        pool = Pool()
        Company = pool.get('company.company')
        company_id = Transaction().context.get('company')
        if company_id:
            company = Company(company_id)
            return company.currency.digits
class Statement(Workflow, ModelSQL, ModelView):
    'Account Statement'
    __name__ = 'account.statement'

    _states = {'readonly': Eval('state') != 'draft'}
    _balance_states = _states.copy()
    _balance_states.update({
            'invisible': ~Eval('validation', '').in_(['balance']),
            'required': Eval('validation', '').in_(['balance']),
            })
    _amount_states = _states.copy()
    _amount_states.update({
            'invisible': ~Eval('validation', '').in_(['amount']),
            'required': Eval('validation', '').in_(['amount']),
            })
    _number_states = _states.copy()
    _number_states.update({
            'invisible': ~Eval('validation', '').in_(['number_of_lines']),
            'required': Eval('validation', '').in_(['number_of_lines']),
            })

    name = fields.Char('Name', required=True)
    company = fields.Many2One(
        'company.company', "Company", required=True, select=True,
        states=_states)
    journal = fields.Many2One('account.statement.journal', 'Journal',
        required=True, select=True,
        domain=[
            ('company', '=', Eval('company', -1)),
            ],
        states={
            'readonly': (Eval('state') != 'draft') | Eval('lines', [0]),
            })
    currency = fields.Function(fields.Many2One(
            'currency.currency', "Currency"), 'on_change_with_currency')
    date = fields.Date('Date', required=True, select=True)
    start_balance = Monetary(
        "Start Balance", currency='currency', digits='currency',
        states=_balance_states)
    end_balance = Monetary(
        "End Balance", currency='currency', digits='currency',
        states=_balance_states)
    balance = fields.Function(Monetary(
            "Balance", currency='currency', digits='currency',
            states=_balance_states),
        'on_change_with_balance')
    total_amount = Monetary(
        "Total Amount", currency='currency', digits='currency',
        states=_amount_states)
    number_of_lines = fields.Integer('Number of Lines',
        states=_number_states)
    lines = fields.One2Many('account.statement.line', 'statement',
        'Lines', states={
            'readonly': (Eval('state') != 'draft') | ~Eval('journal'),
            })
    origins = fields.One2Many('account.statement.origin', 'statement',
        "Origins", states={
            'readonly': Eval('state') != 'draft',
            })
    origin_file = fields.Binary(
        "Origin File", readonly=True,
        file_id=file_id, store_prefix=store_prefix)
    origin_file_id = fields.Char("Origin File ID", readonly=True)
    state = fields.Selection([
            ('draft', "Draft"),
            ('validated', "Validated"),
            ('cancelled', "Cancelled"),
            ('posted', "Posted"),
            ], "State", readonly=True, select=True, sort=False)
    validation = fields.Function(fields.Char('Validation'),
        'on_change_with_validation')
    to_reconcile = fields.Function(
        fields.Boolean("To Reconcile"), 'get_to_reconcile')

    del _states
    del _balance_states
    del _amount_states
    del _number_states

    @classmethod
    def __setup__(cls):
        super(Statement, cls).__setup__()
        cls._order[0] = ('id', 'DESC')
        cls._transitions |= set((
                ('draft', 'validated'),
                ('draft', 'cancelled'),
                ('validated', 'posted'),
                ('validated', 'cancelled'),
                ('cancelled', 'draft'),
                ))
        cls._buttons.update({
                'draft': {
                    'invisible': Eval('state') != 'cancelled',
                    'depends': ['state'],
                    },
                'validate_statement': {
                    'invisible': Eval('state') != 'draft',
                    'depends': ['state'],
                    },
                'post': {
                    'invisible': Eval('state') != 'validated',
                    'depends': ['state'],
                    },
                'cancel': {
                    'invisible': ~Eval('state').in_(['draft', 'validated']),
                    'depends': ['state'],
                    },
                'reconcile': {
                    'invisible': Eval('state').in_(['draft', 'cancelled']),
                    'readonly': ~Eval('to_reconcile'),
                    'depends': ['state', 'to_reconcile'],
                    },
                })
        cls.__rpc__.update({
                'post': RPC(
                    readonly=False, instantiate=0, fresh_session=True),
                })

    @classmethod
    def __register__(cls, module_name):
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        sql_table = cls.__table__()

        super(Statement, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)

        # Migration from 3.2: remove required on start/end balance
        table.not_null_action('start_balance', action='remove')
        table.not_null_action('end_balance', action='remove')

        # Migration from 3.2: add required name
        cursor.execute(*sql_table.update([sql_table.name],
                [sql_table.id.cast(cls.name.sql_type().base)],
                where=sql_table.name == Null))

        # Migration from 5.6: rename state cancel to cancelled
        cursor.execute(*sql_table.update(
                [sql_table.state], ['cancelled'],
                where=sql_table.state == 'cancel'))

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

    @staticmethod
    def default_state():
        return 'draft'

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

    @fields.depends('journal', 'state', 'lines')
    def on_change_journal(self):
        if not self.journal:
            return

        statements = self.search([
                ('journal', '=', self.journal.id),
                ], order=[
                ('date', 'DESC'),
                ('id', 'DESC'),
                ], limit=1)
        if not statements:
            return

        statement, = statements
        self.start_balance = statement.end_balance

    @fields.depends('journal')
    def on_change_with_currency(self, name=None):
        if self.journal:
            return self.journal.currency.id

    @fields.depends('start_balance', 'end_balance')
    def on_change_with_balance(self, name=None):
        return ((getattr(self, 'end_balance', 0) or 0)
            - (getattr(self, 'start_balance', 0) or 0))

    @fields.depends('origins', 'lines', 'journal', 'company')
    def on_change_origins(self):
        if not self.journal or not self.origins or not self.company:
            return
        if self.journal.currency != self.company.currency:
            return

        invoices = set()
        for line in self.lines:
            if (line.invoice
                    and line.invoice.currency == self.company.currency):
                invoices.add(line.invoice)
        for origin in self.origins:
            for line in origin.lines:
                if (line.invoice
                        and line.invoice.currency == self.company.currency):
                    invoices.add(line.invoice)
        invoice_id2amount_to_pay = {}
        for invoice in invoices:
            if invoice.type == 'out':
                sign = -1
            else:
                sign = 1
            invoice_id2amount_to_pay[invoice.id] = sign * invoice.amount_to_pay

        origins = list(self.origins)
        for origin in origins:
            lines = list(origin.lines)
            for line in lines:
                if (line.invoice
                        and line.id
                        and line.invoice.id in invoice_id2amount_to_pay):
                    amount_to_pay = invoice_id2amount_to_pay[line.invoice.id]
                    if (amount_to_pay
                            and getattr(line, 'amount', None)
                            and (line.amount >= 0) == (amount_to_pay <= 0)):
                        if abs(line.amount) > abs(amount_to_pay):
                            line.amount = amount_to_pay.copy_sign(line.amount)
                        else:
                            invoice_id2amount_to_pay[line.invoice.id] = (
                                line.amount + amount_to_pay)
                    else:
                        line.invoice = None
            origin.lines = lines
        self.origins = origins

    @fields.depends('lines', 'journal', 'company')
    def on_change_lines(self):
        pool = Pool()
        Line = pool.get('account.statement.line')
        if not self.journal or not self.lines or not self.company:
            return
        if self.journal.currency != self.company.currency:
            return

        invoices = set()
        for line in self.lines:
            if (line.invoice
                    and line.invoice.currency == self.company.currency):
                invoices.add(line.invoice)
        invoice_id2amount_to_pay = {}
        for invoice in invoices:
            if invoice.type == 'out':
                sign = -1
            else:
                sign = 1
            invoice_id2amount_to_pay[invoice.id] = sign * invoice.amount_to_pay

        lines = list(self.lines)
        line_offset = 0
        for index, line in enumerate(self.lines or []):
            if line.invoice and line.id:
                if line.invoice.id not in invoice_id2amount_to_pay:
                    continue
                amount_to_pay = invoice_id2amount_to_pay[line.invoice.id]
                if (amount_to_pay
                        and getattr(line, 'amount', None)
                        and (line.amount >= 0) == (amount_to_pay <= 0)):
                    if abs(line.amount) > abs(amount_to_pay):
                        new_line = Line()
                        for field_name, field in Line._fields.items():
                            if field_name == 'id':
                                continue
                            try:
                                setattr(new_line, field_name,
                                    getattr(line, field_name))
                            except AttributeError:
                                pass
                        new_line.amount = line.amount + amount_to_pay
                        new_line.invoice = None
                        line_offset += 1
                        lines.insert(index + line_offset, new_line)
                        invoice_id2amount_to_pay[line.invoice.id] = 0
                        line.amount = amount_to_pay.copy_sign(line.amount)
                    else:
                        invoice_id2amount_to_pay[line.invoice.id] = (
                            line.amount + amount_to_pay)
                else:
                    line.invoice = None
        self.lines = lines

    @fields.depends('journal')
    def on_change_with_validation(self, name=None):
        if self.journal:
            return self.journal.validation

    def get_to_reconcile(self, name=None):
        return bool(self.lines_to_reconcile)

    @property
    def lines_to_reconcile(self):
        lines = []
        for line in self.lines:
            if line.move:
                for move_line in line.move.lines:
                    if (move_line.account.reconcile
                            and not move_line.reconciliation):
                        lines.append(move_line)
        return lines

    def _group_key(self, line):
        key = (
            ('number', line.number or Unequal()),
            ('date', line.date),
            ('party', line.party),
            )
        return key

    def _get_grouped_line(self):
        "Return Line class for grouped lines"
        lines = self.origins or self.lines
        assert lines

        keys = [k[0] for k in self._group_key(lines[0])]

        class Line(namedtuple('Line', keys + ['lines'])):

            @property
            def amount(self):
                return sum((l.amount for l in self.lines))

            @property
            def descriptions(self):
                done = set()
                for line in self.lines:
                    if line.description and line.description not in done:
                        done.add(line.description)
                        yield line.description
        return Line

    @property
    def grouped_lines(self):
        if self.origins:
            lines = self.origins
        elif self.lines:
            lines = self.lines
        else:
            return
        Line = self._get_grouped_line()
        for key, lines in groupby(lines, key=self._group_key):
            yield Line(**dict(key + (('lines', list(lines)),)))

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree', 'visual', If(Eval('state') == 'cancelled', 'muted', '')),
            ]

    @classmethod
    def delete(cls, statements):
        # Cancel before delete
        cls.cancel(statements)
        for statement in statements:
            if statement.state != 'cancelled':
                raise AccessError(
                    gettext('account_statement.msg_statement_delete_cancel',
                        statement=statement.rec_name))
        super(Statement, cls).delete(statements)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, statements):
        pass

    def validate_balance(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        amount = (self.start_balance
            + sum(l.amount for l in self.lines))
        if amount != self.end_balance:
            lang = Lang.get()
            end_balance = lang.currency(
                self.end_balance, self.journal.currency)
            amount = lang.currency(amount, self.journal.currency)
            raise StatementValidateError(
                gettext('account_statement.msg_statement_wrong_end_balance',
                    statement=self.rec_name,
                    end_balance=end_balance,
                    amount=amount))

    def validate_amount(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        amount = sum(l.amount for l in self.lines)
        if amount != self.total_amount:
            lang = Lang.get()
            total_amount = lang.currency(
                self.total_amount, self.journal.currency)
            amount = lang.currency(amount, self.journal.currency)
            raise StatementValidateError(
                gettext('account_statement.msg_statement_wrong_total_amount',
                    statement=self.rec_name,
                    total_amount=total_amount,
                    amount=amount))

    def validate_number_of_lines(self):
        number = len(list(self.grouped_lines))
        if number > self.number_of_lines:
            raise StatementValidateError(
                gettext('account_statement'
                    '.msg_statement_wrong_number_of_lines_remove',
                    statement=self.rec_name,
                    n=number - self.number_of_lines))
        elif number < self.number_of_lines:
            raise StatementValidateError(
                gettext('account_statement'
                    '.msg_statement_wrong_number_of_lines_remove',
                    statement=self.rec_name,
                    n=self.number_of_lines - number))

    @classmethod
    @ModelView.button
    @Workflow.transition('validated')
    def validate_statement(cls, statements):
        pool = Pool()
        Line = pool.get('account.statement.line')
        Warning = pool.get('res.user.warning')
        paid_cancelled_invoice_lines = []
        for statement in statements:
            getattr(statement, 'validate_%s' % statement.validation)()
            paid_cancelled_invoice_lines.extend(l for l in statement.lines
                if l.invoice and l.invoice.state in {'cancelled', 'paid'})

        if paid_cancelled_invoice_lines:
            warning_key = Warning.format(
                'statement_paid_cancelled_invoice_lines',
                paid_cancelled_invoice_lines)
            if Warning.check(warning_key):
                raise StatementValidateWarning(warning_key,
                    gettext('account_statement'
                        '.msg_statement_invoice_paid_cancelled'))
            Line.write(paid_cancelled_invoice_lines, {
                    'related_to': None,
                    })

        cls.create_move(statements)

        cls.write(statements, {
                'state': 'validated',
                })
        common_lines = [l for l in Line.search([
                    ('statement.state', '=', 'draft'),
                    ('related_to.state', 'in', ['posted', 'paid'],
                        'account.invoice'),
                    ])
            if l.invoice.reconciled]
        if common_lines:
            warning_key = '_'.join(str(l.id) for l in common_lines)
            if Warning.check(warning_key):
                raise StatementValidateWarning(warning_key,
                    gettext('account_statement'
                        '.msg_statement_paid_invoice_draft'))
            Line.write(common_lines, {
                    'related_to': None,
                    })

    @classmethod
    def create_move(cls, statements):
        '''Create move for the statements and try to reconcile the lines.
        Returns the list of move, statement and lines
        '''
        pool = Pool()
        Line = pool.get('account.statement.line')
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')

        moves = []
        for statement in statements:
            for key, lines in groupby(
                    statement.lines, key=statement._group_key):
                lines = list(lines)
                key = dict(key)
                move = statement._get_move(key)
                moves.append((move, statement, lines))

        Move.save([m for m, _, _ in moves])

        to_write = []
        for move, _, lines in moves:
            to_write.append(lines)
            to_write.append({
                    'move': move.id,
                    })
        if to_write:
            Line.write(*to_write)

        move_lines = []
        for move, statement, lines in moves:
            amount = 0
            amount_second_currency = 0
            for line in lines:
                move_line = line.get_move_line()
                move_line.move = move
                amount += move_line.debit - move_line.credit
                if move_line.amount_second_currency:
                    amount_second_currency += move_line.amount_second_currency
                move_lines.append((move_line, line))

            move_line = statement._get_move_line(
                amount, amount_second_currency, lines)
            move_line.move = move
            move_lines.append((move_line, None))

        MoveLine.save([l for l, _ in move_lines])

        Line.reconcile(move_lines)
        return moves

    def _get_move(self, key):
        'Return Move for the grouping key'
        pool = Pool()
        Move = pool.get('account.move')
        Period = pool.get('account.period')

        period_id = Period.find(self.company.id, date=key['date'])
        return Move(
            period=period_id,
            journal=self.journal.journal,
            date=key['date'],
            origin=self,
            company=self.company,
            description=str(key['number']),
            )

    def _get_move_line(self, amount, amount_second_currency, lines):
        'Return counterpart Move Line for the amount'
        pool = Pool()
        MoveLine = pool.get('account.move.line')

        if self.journal.currency != self.company.currency:
            second_currency = self.journal.currency
            amount_second_currency *= -1
        else:
            second_currency = None
            amount_second_currency = None

        descriptions = {l.description for l in lines}
        if len(descriptions) == 1:
            description, = descriptions
        else:
            description = ''

        return MoveLine(
            debit=abs(amount) if amount < 0 else 0,
            credit=abs(amount) if amount > 0 else 0,
            account=self.journal.account,
            second_currency=second_currency,
            amount_second_currency=amount_second_currency,
            description=description,
            )

    @classmethod
    @ModelView.button
    @Workflow.transition('posted')
    def post(cls, statements):
        pool = Pool()
        Lang = pool.get('ir.lang')
        StatementLine = pool.get('account.statement.line')
        for statement in statements:
            for origin in statement.origins:
                if origin.pending_amount:
                    lang = Lang.get()
                    amount = lang.currency(
                        origin.pending_amount, statement.journal.currency)
                    raise StatementPostError(
                        gettext('account_statement'
                            '.msg_statement_post_pending_amount',
                            statement=statement.rec_name,
                            amount=amount,
                            origin=origin.rec_name))
        # Write state to skip statement test on Move.post
        cls.write(statements, {'state': 'posted'})
        lines = [l for s in statements for l in s.lines]
        StatementLine.post_move(lines)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, statements):
        StatementLine = Pool().get('account.statement.line')

        lines = [l for s in statements for l in s.lines]
        StatementLine.delete_move(lines)

    @classmethod
    @ModelView.button_action('account_statement.act_reconcile')
    def reconcile(cls, statements):
        pass

    @classmethod
    def copy(cls, statements, default=None):
        default = default.copy() if default is not None else {}
        new_statements = []
        for origins, sub_statements in groupby(
                statements, key=lambda s: bool(s.origins)):
            sub_statements = list(sub_statements)
            sub_default = default.copy()
            if origins:
                sub_default.setdefault('lines')
            new_statements.extend(super().copy(
                    statements, default=sub_default))
        return new_statements
class Transfer(Workflow, ModelSQL, ModelView):
    "Transfer between Cash/Bank"
    __name__ = "cash_bank.transfer"
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        states={
            'readonly': True,
        },
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        select=True)
    date = fields.Date('Date', required=True, states=_STATES, depends=_DEPENDS)
    reference = fields.Char('Reference', size=None)
    description = fields.Char('Description', size=None)
    cash_bank_from = fields.Many2One(
        'cash_bank.cash_bank',
        'From',
        required=True,
        domain=[
            ('company', '=', Eval('company')),
        ],
        states={
            'readonly': Or(Eval('state') != 'draft', Eval('documents')),
        },
        depends=_DEPENDS + ['company', 'documents'])
    type_from = fields.Many2One(
        'cash_bank.receipt_type',
        'Type',
        required=True,
        domain=[
            If(Bool(Eval('cash_bank_from')),
               [('cash_bank', '=', Eval('cash_bank_from'))],
               [('cash_bank', '=', -1)]), ('type', '=', 'out')
        ],
        states={
            'readonly': Or(Eval('state') != 'draft', Eval('documents')),
        },
        depends=_DEPENDS + ['cash_bank_from', 'documents'])
    cash_bank_to = fields.Many2One(
        'cash_bank.cash_bank',
        'To',
        required=True,
        domain=[
            ('company', '=', Eval('company')),
            If(Bool(Eval('cash_bank_from')),
               [('id', '!=', Eval('cash_bank_from'))], [('id', '=', -1)]),
        ],
        states={
            'readonly': Or(Eval('state') != 'draft', Eval('documents')),
        },
        depends=_DEPENDS + ['company', 'cash_bank_from', 'documents'])
    type_to = fields.Many2One(
        'cash_bank.receipt_type',
        'Type',
        required=True,
        domain=[
            If(Bool(Eval('cash_bank_to')),
               [('cash_bank', '=', Eval('cash_bank_to'))],
               [('cash_bank', '=', -1)]), ('type', '=', 'in')
        ],
        states={
            'readonly': Or(Eval('state') != 'draft', Eval('documents')),
        },
        depends=_DEPENDS + ['cash_bank_to', 'documents'])
    currency = fields.Many2One('currency.currency',
                               'Currency',
                               required=True,
                               states={
                                   'readonly': True,
                               },
                               depends=['state', 'documents'])
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'on_change_with_currency_digits')
    cash = fields.Numeric('Cash',
                          digits=(16, Eval('currency_digits', 2)),
                          states=_STATES,
                          depends=_DEPENDS + ['currency_digits'])
    documents = fields.Many2Many(
        'cash_bank.document-cash_bank.transfer',
        'transfer',
        'document',
        'Documents',
        domain=[
            If(Bool(Eval('type_from')), [
                If(
                    Eval('state') != 'posted',
                    [('convertion', '=', None),
                     If(
                         Eval('state') == 'draft', [
                             ('last_receipt.cash_bank.id', '=',
                              Eval('cash_bank_from')),
                         ], [('last_receipt.cash_bank.id', '=',
                              Eval('cash_bank_to'))])], [('id', '!=', -1)])
            ], [('id', '=', -1)]),
        ],
        states=_STATES,
        depends=_DEPENDS + ['type_from', 'cash_bank_from', 'cash_bank_to'])
    total_documents = fields.Function(
        fields.Numeric('Total Documents',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_total_documents')
    total = fields.Function(
        fields.Numeric('Total',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_total')
    receipt_from = fields.Many2One('cash_bank.receipt',
                                   'Receipt From',
                                   readonly=True)
    receipt_to = fields.Many2One('cash_bank.receipt',
                                 'Receipt To',
                                 readonly=True)
    state = fields.Selection(STATES, 'State', readonly=True, required=True)
    logs = fields.One2Many('cash_bank.transfer.log_action',
                           'resource',
                           'Logs',
                           readonly=True)

    @classmethod
    def __register__(cls, module_name):
        super(Transfer, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)
        if table.column_exist('party'):
            table.drop_column('party')

    @classmethod
    def __setup__(cls):
        super(Transfer, cls).__setup__()
        cls._order[0] = ('id', 'DESC')

        cls._transitions |= set((
            ('draft', 'confirmed'),
            ('confirmed', 'posted'),
            ('confirmed', 'cancel'),
            ('cancel', 'draft'),
        ))

        cls._buttons.update({
            'cancel': {
                'invisible': ~Eval('state').in_(['confirmed']),
            },
            'confirm': {
                'invisible': ~Eval('state').in_(['draft']),
            },
            'post': {
                'invisible': ~Eval('state').in_(['confirmed']),
            },
            'draft': {
                'invisible':
                ~Eval('state').in_(['cancel']),
                'icon':
                If(
                    Eval('state') == 'cancel', 'tryton-clear',
                    'tryton-go-previous'),
            },
        })

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

    @staticmethod
    def default_cash():
        return Decimal('0.0')

    @staticmethod
    def default_total_documents():
        return Decimal('0.0')

    @staticmethod
    def default_total():
        return Decimal('0.0')

    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_currency():
        Company = Pool().get('company.company')
        company = Transaction().context.get('company')
        if company:
            company = Company(company)
            return company.currency.id

    @staticmethod
    def default_currency_digits():
        Company = Pool().get('company.company')
        company = Transaction().context.get('company')
        if company:
            company = Company(company)
            return company.currency.digits
        return 2

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

    @fields.depends('cash_bank_from', 'type_from', 'cash_bank_to', 'type_to')
    def on_change_company(self):
        self.cash_bank_from = None
        self.type_from = None
        self.cash_bank_to = None
        self.type_to = None

    @fields.depends('type_from', 'type_to', 'cash_bank_to')
    def on_change_cash_bank_from(self):
        self.type_from = None
        self.cash_bank_to = None
        self.type_to = None

    @fields.depends('type_to')
    def on_change_cash_bank_to(self):
        self.type_to = None

    @fields.depends('type_from', 'cash_bank_from')
    def on_change_type_from(self):
        if self.type_from:
            self.cash_bank_from = self.type_from.cash_bank

    @fields.depends('type_to', 'cash_bank_to')
    def on_change_type_to(self):
        if self.type_to:
            self.cash_bank_to = self.type_to.cash_bank

    @fields.depends('cash', 'total_documents')
    def on_change_cash(self):
        if not self.cash:
            self.cash = Decimal('0.0')
        self._set_total()

    @fields.depends('documents', 'cash', 'total_documents')
    def on_change_documents(self):
        self.total_documents = self.get_total_documents()
        self._set_total()

    def get_total_documents(self, name=None):
        total = Decimal('0.0')
        if self.documents:
            for doc in self.documents:
                if doc.amount:
                    total += doc.amount
        return total

    def get_total(self, name=None):
        total = self.cash + self.total_documents
        return total

    def _set_total(self):
        self.total = Decimal('0.0')
        if self.cash:
            self.total += self.cash
        if self.total_documents:
            self.total += self.total_documents

    def _new_receipt(self, cash_bank, type_):
        Receipt = Pool().get('cash_bank.receipt')
        party = None
        if type_.party_required:
            party = self.company.party
        return Receipt(company=self.company,
                       currency=self.currency,
                       date=self.date,
                       cash_bank=cash_bank,
                       type=type_,
                       party=party,
                       reference=self.reference,
                       description=self.description,
                       cash=self.cash)

    def _create_line(self, type_, amount, transfer_account):
        Line = Pool().get('cash_bank.receipt.line')
        line = Line()
        line.description = 'Transfer'
        line.account = transfer_account
        line.amount = amount
        return line

    def _get_doc(self, receipt, docs):
        Docs = Pool().get('cash_bank.document')
        for doc in docs:
            doc.last_receipt = receipt
        Docs.save(docs)
        return docs

    def _create_receipt(self, cash_bank, type_, trasnfer_account, docs):
        receipt = self._new_receipt(cash_bank, type_)
        receipt.documents = self._get_doc(receipt, docs)
        receipt.save()
        receipt.lines = [
            self._create_line(type_, receipt.total, trasnfer_account),
        ]
        receipt.save()
        return receipt

    def create_receipts(self):
        pool = Pool()
        Config = pool.get('cash_bank.configuration')
        Receipt = pool.get('cash_bank.receipt')

        config = Config(1)
        transfer_account = config.account_transfer

        receipt_from = self._create_receipt(self.cash_bank_from,
                                            self.type_from, transfer_account,
                                            self.documents)
        Receipt.confirm([receipt_from])

        receipt_to = self._create_receipt(self.cash_bank_to, self.type_to,
                                          transfer_account, self.documents)
        Receipt.confirm([receipt_to])

        self.receipt_from = receipt_from
        self.receipt_to = receipt_to

    @classmethod
    def set_transfer(cls, receipts, transfer):
        for receipt in receipts:
            receipt.transfer = transfer
            receipt.save()

    @classmethod
    def create(cls, vlist):
        transfers = super(Transfer, cls).create(vlist)
        write_log('Created', transfers)
        return transfers

    @classmethod
    def delete(cls, transfers):
        for transfer in transfers:
            if transfer.state != 'draft':
                raise UserError(
                    gettext('cash_bank.msg_delete_document_cash_bank',
                            doc_name='Transfer',
                            doc_number=transfer.rec_name,
                            state='Draft'))
        super(Transfer, cls).delete(transfers)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, transfers):
        Receipt = Pool().get('cash_bank.receipt')
        for transfer in transfers:
            receipt_from = transfer.receipt_from
            receipt_to = transfer.receipt_to
            transfer.receipt_from = None
            transfer.receipt_to = None
            Receipt.draft([receipt_from, receipt_to])
            Receipt.delete([receipt_to])
            Receipt.delete([receipt_from])
        cls.save(transfers)
        write_log('Draft', transfers)

    @classmethod
    @ModelView.button
    @Workflow.transition('confirmed')
    def confirm(cls, transfers):
        for transfer in transfers:
            if transfer.total <= 0:
                raise UserError(gettext('cash_bank.msg_no_total_cash_bank'))
            transfer.create_receipts()
            cls.set_transfer([transfer.receipt_from, transfer.receipt_to],
                             transfer)
        cls.save(transfers)  # Update receipts values
        write_log('Confirmed', transfers)

    @classmethod
    @ModelView.button
    @Workflow.transition('posted')
    def post(cls, transfers):
        Receipt = Pool().get('cash_bank.receipt')
        rcps = []
        for transfer in transfers:
            rcps += [transfer.receipt_from, transfer.receipt_to]
        Receipt.post(rcps)
        write_log('Posted', transfers)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, transfers):
        Receipt = Pool().get('cash_bank.receipt')
        rcps = []
        for transfer in transfers:
            rcps += [transfer.receipt_from, transfer.receipt_to]
        Receipt.cancel(rcps)
        write_log('Cancelled', transfers)
Exemple #6
0
class ModelSQLRequiredField(ModelSQL):
    'model with a required field'
    __name__ = 'test.modelsql'

    integer = fields.Integer(string="integer", required=True)
    desc = fields.Char(string="desc", required=True)
Exemple #7
0
class Convertion(Workflow, ModelSQL, ModelView):
    "Cash/Bank Convert Documents to Cash"
    __name__ = "cash_bank.convertion"
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        states={
            'readonly': True,
        },
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        select=True)
    number = fields.Char('Number', size=None, readonly=True, select=True)
    cash_bank = fields.Many2One('cash_bank.cash_bank',
                                'Cash',
                                required=True,
                                domain=[
                                    ('company', '=', Eval('company')),
                                    ('type', '=', 'cash'),
                                ],
                                states={
                                    'readonly':
                                    Or(
                                        Eval('state') != 'draft',
                                        Eval('documents')),
                                },
                                depends=_DEPENDS + ['company', 'documents'])
    date = fields.Date('Date', required=True, states=_STATES, depends=_DEPENDS)
    description = fields.Char('Description', size=None)
    currency = fields.Many2One('currency.currency',
                               'Currency',
                               required=True,
                               states={
                                   'readonly': True,
                               },
                               depends=['state', 'documents'])
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'on_change_with_currency_digits')
    documents = fields.Many2Many(
        'cash_bank.document-cash_bank.convertion',
        'convertion',
        'document',
        'Documents',
        domain=[('last_receipt', '!=', None),
                ('last_receipt.cash_bank.id', '=', Eval('cash_bank')),
                If(
                    Eval('state') == 'draft', [
                        ('convertion', '=', None),
                    ], [
                        ('convertion', '!=', None),
                        ('convertion.id', '=', Eval('id')),
                    ])],
        states=_STATES,
        depends=_DEPENDS + ['cash_bank', 'id'])
    total_documents = fields.Function(
        fields.Numeric('Total Documents',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_total_documents')
    state = fields.Selection(STATES, 'State', readonly=True, required=True)
    logs = fields.One2Many('cash_bank.convertion.log_action',
                           'resource',
                           'Logs',
                           readonly=True)

    @classmethod
    def __setup__(cls):
        super(Convertion, cls).__setup__()
        cls._order[0] = ('id', 'DESC')

        cls._transitions |= set((
            ('draft', 'confirmed'),
            ('confirmed', 'cancel'),
            ('cancel', 'draft'),
        ))

        cls._buttons.update({
            'cancel': {
                'invisible': ~Eval('state').in_(['confirmed']),
            },
            'confirm': {
                'invisible': ~Eval('state').in_(['draft']),
            },
            'draft': {
                'invisible':
                ~Eval('state').in_(['cancel']),
                'icon':
                If(
                    Eval('state') == 'cancel', 'tryton-clear',
                    'tryton-go-previous'),
            },
        })

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

    @staticmethod
    def default_total_documents():
        return Decimal('0.0')

    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_currency():
        Company = Pool().get('company.company')
        company = Transaction().context.get('company')
        if company:
            company = Company(company)
            return company.currency.id

    @staticmethod
    def default_currency_digits():
        Company = Pool().get('company.company')
        company = Transaction().context.get('company')
        if company:
            company = Company(company)
            return company.currency.digits
        return 2

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

    @fields.depends('documents')
    def on_change_documents(self):
        self.total_documents = self.get_total_documents()

    def get_total_documents(self, name=None):
        total = Decimal('0.0')
        if self.documents:
            for doc in self.documents:
                if doc.amount:
                    total += doc.amount
        return total

    def get_rec_name(self, name):
        if self.number:
            return self.number
        return str(self.id)

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

    @classmethod
    def set_number(cls, convertions):
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Config = pool.get('cash_bank.configuration')
        config = Config(1)
        for convertion in convertions:
            if convertion.number:
                continue
            convertion.number = Sequence.get_id(config.convertion_seq.id)
        cls.save(convertions)

    @classmethod
    def create(cls, vlist):
        convertions = super(Convertion, cls).create(vlist)
        write_log('Created', convertions)
        return convertions

    @classmethod
    def delete(cls, convertions):
        Document = Pool().get('cash_bank.document')
        docs = []
        for convertion in convertions:
            if convertion.state != 'draft':
                raise UserError(
                    gettext('cash_bank.msg_delete_document_cash_bank',
                            doc_name='Convertion',
                            doc_number=convertion.rec_name,
                            state='Draft'))
            for doc in convertion.documents:
                doc.convertion = None
                docs.append(doc)
                write_log('Convertion ' + convertion.rec_name + ' deleted.',
                          [doc])
        Document.save(docs)
        super(Convertion, cls).delete(convertions)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, convertions):
        Document = Pool().get('cash_bank.document')
        docs = []
        for convertion in convertions:
            for doc in convertion.documents:
                doc.convertion = None
                docs.append(doc)
                write_log('Convertion ' + convertion.rec_name + ' to Draft.',
                          [doc])
        Document.save(docs)
        write_log('Draft', convertions)

    @classmethod
    @ModelView.button
    @Workflow.transition('confirmed')
    def confirm(cls, convertions):
        Document = Pool().get('cash_bank.document')
        docs = []
        for convertion in convertions:
            for doc in convertion.documents:
                doc.convertion = convertion
                write_log('Convertion ' + convertion.rec_name + ' confirmed.',
                          [doc])
                docs.append(doc)
        Document.save(docs)
        cls.set_number(convertions)
        write_log('Confirmed', convertions)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, convertions):
        write_log('Cancelled', convertions)
Exemple #8
0
class IntegerRequired(ModelSQL):
    'Integer Required'
    __name__ = 'test.integer_required'
    integer = fields.Integer(string='Integer',
                             help='Test integer',
                             required=True)
Exemple #9
0
class IntegerDomain(ModelSQL):
    'Integer Domain'
    __name__ = 'test.integer_domain'
    integer = fields.Integer('Integer', domain=[('integer', '>', 42)])
Exemple #10
0
class Statement(Workflow, ModelSQL, ModelView):
    'Account Statement'
    __name__ = 'account.statement'
    name = fields.Char('Name', required=True)
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        select=True,
        states=_STATES,
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        depends=_DEPENDS)
    journal = fields.Many2One('account.statement.journal',
                              'Journal',
                              required=True,
                              select=True,
                              domain=[
                                  ('company', '=', Eval('company', -1)),
                              ],
                              states={
                                  'readonly': (Eval('state') != 'draft')
                                  | Eval('lines', [0]),
                              },
                              depends=['state', 'company'])
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'on_change_with_currency_digits')
    date = fields.Date('Date', required=True, select=True)
    start_balance = fields.Numeric('Start Balance',
                                   digits=(16, Eval('currency_digits', 2)),
                                   states=_BALANCE_STATES,
                                   depends=_BALANCE_DEPENDS +
                                   ['currency_digits'])
    end_balance = fields.Numeric('End Balance',
                                 digits=(16, Eval('currency_digits', 2)),
                                 states=_BALANCE_STATES,
                                 depends=_BALANCE_DEPENDS +
                                 ['currency_digits'])
    balance = fields.Function(
        fields.Numeric('Balance',
                       digits=(16, Eval('currency_digits', 2)),
                       states=_BALANCE_STATES,
                       depends=_BALANCE_DEPENDS + ['currency_digits']),
        'on_change_with_balance')
    total_amount = fields.Numeric('Total Amount',
                                  digits=(16, Eval('currency_digits', 2)),
                                  states=_AMOUNT_STATES,
                                  depends=_AMOUNT_DEPENDS +
                                  ['currency_digits'])
    number_of_lines = fields.Integer('Number of Lines',
                                     states=_NUMBER_STATES,
                                     depends=_NUMBER_DEPENDS)
    lines = fields.One2Many('account.statement.line',
                            'statement',
                            'Lines',
                            states={
                                'readonly':
                                (Eval('state') != 'draft') | ~Eval('journal'),
                            },
                            depends=['state', 'journal'])
    origins = fields.One2Many('account.statement.origin',
                              'statement',
                              "Origins",
                              states={
                                  'readonly': Eval('state') != 'draft',
                              },
                              depends=['state'])
    origin_file = fields.Binary("Origin File",
                                readonly=True,
                                file_id=file_id,
                                store_prefix=store_prefix)
    origin_file_id = fields.Char("Origin File ID", readonly=True)
    state = fields.Selection(STATES, 'State', readonly=True, select=True)
    validation = fields.Function(fields.Char('Validation'),
                                 'on_change_with_validation')

    @classmethod
    def __setup__(cls):
        super(Statement, cls).__setup__()
        cls._order[0] = ('id', 'DESC')
        cls._error_messages.update({
            'wrong_end_balance':
            'End Balance must be "%s".',
            'wrong_total_amount':
            'Total Amount must be "%s".',
            'wrong_number_of_lines':
            'Number of Lines must be "%s".',
            'delete_cancel': ('Statement "%s" must be cancelled before '
                              'deletion.'),
            'paid_invoice_draft_statement': ('There are paid invoices on '
                                             'draft statements.'),
            'debit_credit_account_statement_journal':
            ('Please provide '
             'debit and credit account on statement journal "%s".'),
            'post_with_pending_amount':
            ('Origin line "%(origin)s" '
             'of statement "%(statement)s" still has a pending amount '
             'of "%(amount)s".'),
        })
        cls._transitions |= set((
            ('draft', 'validated'),
            ('draft', 'cancel'),
            ('validated', 'posted'),
            ('validated', 'cancel'),
            ('cancel', 'draft'),
        ))
        cls._buttons.update({
            'draft': {
                'invisible': Eval('state') != 'cancel',
            },
            'validate_statement': {
                'invisible': Eval('state') != 'draft',
            },
            'post': {
                'invisible': Eval('state') != 'validated',
            },
            'cancel': {
                'invisible': ~Eval('state').in_(['draft', 'validated']),
            },
        })

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        sql_table = cls.__table__()

        # Migration from 1.8: new field company
        table = TableHandler(cls, module_name)
        company_exist = table.column_exist('company')

        super(Statement, cls).__register__(module_name)

        # Migration from 1.8: fill new field company
        if not company_exist:
            offset = 0
            limit = transaction.database.IN_MAX
            statements = True
            while statements:
                statements = cls.search([], offset=offset, limit=limit)
                offset += limit
                for statement in statements:
                    cls.write([statement], {
                        'company': statement.journal.company.id,
                    })
            table = TableHandler(cls, module_name)
            table.not_null_action('company', action='add')

        # Migration from 3.2: remove required on start/end balance
        table.not_null_action('start_balance', action='remove')
        table.not_null_action('end_balance', action='remove')

        # Migration from 3.2: add required name
        cursor.execute(*sql_table.update(
            [sql_table.name], [sql_table.id.cast(cls.name.sql_type().base)],
            where=sql_table.name == Null))

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

    @staticmethod
    def default_state():
        return 'draft'

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

    @staticmethod
    def default_currency_digits():
        Company = Pool().get('company.company')
        if Transaction().context.get('company'):
            company = Company(Transaction().context['company'])
            return company.currency.digits
        return 2

    @fields.depends('journal', 'state', 'lines')
    def on_change_journal(self):
        if not self.journal:
            return

        statements = self.search([
            ('journal', '=', self.journal.id),
        ],
                                 order=[
                                     ('date', 'DESC'),
                                     ('id', 'DESC'),
                                 ],
                                 limit=1)
        if not statements:
            return

        statement, = statements
        self.start_balance = statement.end_balance

    @fields.depends('journal')
    def on_change_with_currency_digits(self, name=None):
        if self.journal:
            return self.journal.currency.digits
        return 2

    def get_end_balance(self, name):
        end_balance = self.start_balance
        for line in self.lines:
            end_balance += line.amount
        return end_balance

    @fields.depends('start_balance', 'end_balance')
    def on_change_with_balance(self, name=None):
        return ((getattr(self, 'end_balance', 0) or 0) -
                (getattr(self, 'start_balance', 0) or 0))

    @fields.depends('lines', 'journal')
    def on_change_lines(self):
        pool = Pool()
        Currency = pool.get('currency.currency')
        Line = pool.get('account.statement.line')
        if self.journal and self.lines:
            invoices = set()
            for line in self.lines:
                if getattr(line, 'invoice', None):
                    invoices.add(line.invoice)
            invoice_id2amount_to_pay = {}
            for invoice in invoices:
                with Transaction().set_context(date=invoice.currency_date):
                    if invoice.type == 'out':
                        sign = -1
                    else:
                        sign = 1
                    invoice_id2amount_to_pay[
                        invoice.id] = sign * (Currency.compute(
                            invoice.currency, invoice.amount_to_pay,
                            self.journal.currency))

            lines = list(self.lines)
            line_offset = 0
            for index, line in enumerate(self.lines or []):
                if getattr(line, 'invoice', None) and line.id:
                    amount_to_pay = invoice_id2amount_to_pay[line.invoice.id]
                    if (not self.journal.currency.is_zero(amount_to_pay)
                            and getattr(line, 'amount', None)
                            and (line.amount >= 0) == (amount_to_pay <= 0)):
                        if abs(line.amount) > abs(amount_to_pay):
                            new_line = Line()
                            for field_name, field in Line._fields.iteritems():
                                if field_name == 'id':
                                    continue
                                try:
                                    setattr(new_line, field_name,
                                            getattr(line, field_name))
                                except AttributeError:
                                    pass
                            new_line.amount = line.amount + amount_to_pay
                            new_line.invoice = None
                            line_offset += 1
                            lines.insert(index + line_offset, new_line)
                            invoice_id2amount_to_pay[line.invoice.id] = 0
                            line.amount = amount_to_pay.copy_sign(line.amount)
                        else:
                            invoice_id2amount_to_pay[line.invoice.id] = (
                                line.amount + amount_to_pay)
                    else:
                        line.invoice = None
            self.lines = lines

    @fields.depends('journal')
    def on_change_with_validation(self, name=None):
        if self.journal:
            return self.journal.validation

    def _group_key(self, line):
        key = (
            ('number', line.number or Unequal()),
            ('date', line.date),
            ('party', line.party),
        )
        return key

    def _get_grouped_line(self):
        "Return Line class for grouped lines"
        assert self.lines

        keys = [k[0] for k in self._group_key(self.lines[0])]

        class Line(namedtuple('Line', keys + ['lines'])):
            @property
            def amount(self):
                return sum((l.amount for l in self.lines))

            @property
            def descriptions(self):
                done = set()
                for line in self.lines:
                    if line.description and line.description not in done:
                        done.add(line.description)
                        yield line.description

        return Line

    @property
    def grouped_lines(self):
        if self.origins:
            for origin in self.origins:
                yield origin
        elif self.lines:
            Line = self._get_grouped_line()
            for key, lines in groupby(self.lines, key=self._group_key):
                yield Line(**dict(key + (('lines', list(lines)), )))

    @classmethod
    def delete(cls, statements):
        # Cancel before delete
        cls.cancel(statements)
        for statement in statements:
            if statement.state != 'cancel':
                cls.raise_user_error('delete_cancel', (statement.rec_name, ))
        super(Statement, cls).delete(statements)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, statements):
        pass

    def validate_balance(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        end_balance = (self.start_balance + sum(l.amount for l in self.lines))
        if end_balance != self.end_balance:
            lang, = Lang.search([
                ('code', '=', Transaction().language),
            ])
            amount = Lang.format(
                lang, '%.' + str(self.journal.currency.digits) + 'f',
                end_balance, True)
            self.raise_user_error('wrong_end_balance', amount)

    def validate_amount(self):
        pool = Pool()
        Lang = pool.get('ir.lang')

        amount = sum(l.amount for l in self.lines)
        if amount != self.total_amount:
            lang, = Lang.search([
                ('code', '=', Transaction().language),
            ])
            amount = Lang.format(
                lang, '%.' + str(self.journal.currency.digits) + 'f', amount,
                True)
            self.raise_user_error('wrong_total_amount', amount)

    def validate_number_of_lines(self):
        number = len(list(self.grouped_lines))
        if number != self.number_of_lines:
            self.raise_user_error('wrong_number_of_lines', number)

    @classmethod
    @ModelView.button
    @Workflow.transition('validated')
    def validate_statement(cls, statements):
        pool = Pool()
        Line = pool.get('account.statement.line')

        for statement in statements:
            getattr(statement, 'validate_%s' % statement.validation)()

        cls.create_move(statements)

        cls.write(statements, {
            'state': 'validated',
        })
        common_lines = Line.search([
            ('statement.state', '=', 'draft'),
            ('invoice.state', '=', 'paid'),
        ])
        if common_lines:
            warning_key = '_'.join(str(l.id) for l in common_lines)
            cls.raise_user_warning(warning_key, 'paid_invoice_draft_statement')
            Line.write(common_lines, {
                'invoice': None,
            })

    @classmethod
    def create_move(cls, statements):
        '''Create move for the statements and try to reconcile the lines.
        Returns the list of move, statement and lines
        '''
        pool = Pool()
        Line = pool.get('account.statement.line')
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')

        moves = []
        for statement in statements:
            for key, lines in groupby(statement.lines,
                                      key=statement._group_key):
                lines = list(lines)
                key = dict(key)
                move = statement._get_move(key)
                moves.append((move, statement, lines))

        Move.save([m for m, _, _ in moves])

        to_write = []
        for move, _, lines in moves:
            to_write.append(lines)
            to_write.append({
                'move': move.id,
            })
        if to_write:
            Line.write(*to_write)

        move_lines = []
        for move, statement, lines in moves:
            amount = 0
            amount_second_currency = 0
            for line in lines:
                move_line = line.get_move_line()
                move_line.move = move
                amount += move_line.debit - move_line.credit
                if move_line.amount_second_currency:
                    amount_second_currency += move_line.amount_second_currency
                move_lines.append((move_line, line))

            move_line = statement._get_move_line(amount,
                                                 amount_second_currency, lines)
            move_line.move = move
            move_lines.append((move_line, None))

        MoveLine.save([l for l, _ in move_lines])

        for move_line, line in move_lines:
            if line:
                line.reconcile(move_line)
        return moves

    def _get_move(self, key):
        'Return Move for the grouping key'
        pool = Pool()
        Move = pool.get('account.move')
        Period = pool.get('account.period')

        period_id = Period.find(self.company.id, date=key['date'])
        return Move(
            period=period_id,
            journal=self.journal.journal,
            date=key['date'],
            origin=self,
            company=self.company,
            description=unicode(key['number']),
        )

    def _get_move_line(self, amount, amount_second_currency, lines):
        'Return counterpart Move Line for the amount'
        pool = Pool()
        MoveLine = pool.get('account.move.line')

        if amount < 0:
            account = self.journal.journal.debit_account
        else:
            account = self.journal.journal.credit_account

        if not account:
            self.raise_user_error('debit_credit_account_statement_journal',
                                  (self.journal.rec_name, ))

        if self.journal.currency != self.company.currency:
            second_currency = self.journal.currency
            amount_second_currency *= -1
        else:
            second_currency = None
            amount_second_currency = None

        descriptions = {l.description for l in lines}
        if len(descriptions) == 1:
            description, = descriptions
        else:
            description = ''

        return MoveLine(
            debit=abs(amount) if amount < 0 else 0,
            credit=abs(amount) if amount > 0 else 0,
            account=account,
            second_currency=second_currency,
            amount_second_currency=amount_second_currency,
            description=description,
        )

    @classmethod
    @ModelView.button
    @Workflow.transition('posted')
    def post(cls, statements):
        StatementLine = Pool().get('account.statement.line')
        for statement in statements:
            for origin in statement.origins:
                if origin.pending_amount:
                    cls.raise_user_error(
                        'post_with_pending_amount', {
                            'origin': origin.rec_name,
                            'amount': origin.pending_amount,
                            'statement': statement.rec_name,
                        })

        lines = [l for s in statements for l in s.lines]
        StatementLine.post_move(lines)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, statements):
        StatementLine = Pool().get('account.statement.line')

        lines = [l for s in statements for l in s.lines]
        StatementLine.delete_move(lines)
Exemple #11
0
class LineGroup(ModelSQL, ModelView):
    'Account Statement Line Group'
    __name__ = 'account.statement.line.group'
    _rec_name = 'number'
    statement = fields.Many2One('account.statement', 'Statement')
    journal = fields.Function(fields.Many2One('account.statement.journal',
                                              'Journal'),
                              'get_journal',
                              searcher='search_journal')
    number = fields.Char('Number')
    date = fields.Date('Date')
    amount = fields.Numeric('Amount',
                            digits=(16, Eval('currency_digits', 2)),
                            depends=['currency_digits'])
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'), 'get_currency')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'get_currency_digits')
    party = fields.Many2One('party.party', 'Party')
    move = fields.Many2One('account.move', 'Move')

    @classmethod
    def __setup__(cls):
        super(LineGroup, cls).__setup__()
        cls._order.insert(0, ('date', 'DESC'))

    @classmethod
    def _grouped_columns(cls, line):
        return [
            Max(line.statement).as_('statement'),
            Max(line.number).as_('number'),
            Max(line.date).as_('date'),
            Sum(line.amount).as_('amount'),
            Max(line.party).as_('party'),
        ]

    @classmethod
    def table_query(cls):
        pool = Pool()
        Move = pool.get('account.move')
        Line = pool.get('account.statement.line')
        move = Move.__table__()
        line = Line.__table__()

        std_columns = [
            move.id,
            move.create_uid,
            move.create_date,
            move.write_uid,
            move.write_date,
        ]

        columns = (std_columns + [move.id.as_('move')] +
                   cls._grouped_columns(line))
        return move.join(line, condition=move.id == line.move).select(
            *columns,
            where=move.origin.ilike(Statement.__name__ + ',%'),
            group_by=std_columns + [move.id])

    def get_journal(self, name):
        return self.statement.journal.id

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

    def get_currency(self, name):
        return self.statement.journal.currency.id

    def get_currency_digits(self, name):
        return self.statement.journal.currency.digits
Exemple #12
0
class Uom(ModelSQL, ModelView):
    'Unit of measure'
    __name__ = 'product.uom'
    name = fields.Char('Name',
                       size=None,
                       required=True,
                       states=STATES,
                       translate=True,
                       depends=DEPENDS)
    symbol = fields.Char('Symbol',
                         size=10,
                         required=True,
                         states=STATES,
                         translate=True,
                         depends=DEPENDS)
    category = fields.Many2One('product.uom.category',
                               'Category',
                               required=True,
                               ondelete='RESTRICT',
                               states=STATES,
                               depends=DEPENDS)
    rate = fields.Float('Rate',
                        digits=(12, 12),
                        required=True,
                        states=STATES,
                        depends=DEPENDS,
                        help=('The coefficient for the formula:\n'
                              '1 (base unit) = coef (this unit)'))
    factor = fields.Float('Factor',
                          digits=(12, 12),
                          states=STATES,
                          required=True,
                          depends=DEPENDS,
                          help=('The coefficient for the formula:\n'
                                'coef (base unit) = 1 (this unit)'))
    rounding = fields.Float('Rounding Precision',
                            digits=(12, 12),
                            required=True,
                            states=STATES,
                            depends=DEPENDS,
                            domain=[
                                ('rounding', '>', 0),
                            ])
    digits = fields.Integer('Display Digits', required=True)
    active = fields.Boolean('Active')

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().cursor
        model_data = Table('ir_model_data')
        # Migration from 1.6: corrected misspelling of ounce (was once)
        cursor.execute(
            *model_data.update(columns=[model_data.fs_id],
                               values=['uom_ounce'],
                               where=(model_data.fs_id == 'uom_once')
                               & (model_data.module == 'product')))
        super(Uom, cls).__register__(module_name)

    @classmethod
    def __setup__(cls):
        super(Uom, cls).__setup__()
        t = cls.__table__()
        cls._sql_constraints += [
            ('non_zero_rate_factor', Check(t, (t.rate != 0) | (t.factor != 0)),
             'Rate and factor can not be both equal to zero.')
        ]
        cls._order.insert(0, ('name', 'ASC'))
        cls._error_messages.update({
            'change_uom_rate_title': ('You cannot change Rate, Factor or '
                                      'Category on a Unit of Measure. '),
            'change_uom_rate': ('If the UOM is still not used, you can '
                                'delete it otherwise you can deactivate it '
                                'and create a new one.'),
            'invalid_factor_and_rate':
            ('Invalid Factor and Rate values in UOM "%s".'),
        })

    @classmethod
    def check_xml_record(cls, records, values):
        return True

    @staticmethod
    def default_rate():
        return 1.0

    @staticmethod
    def default_factor():
        return 1.0

    @staticmethod
    def default_active():
        return True

    @staticmethod
    def default_rounding():
        return 0.01

    @staticmethod
    def default_digits():
        return 2

    @fields.depends('factor')
    def on_change_factor(self):
        if (self.factor or 0.0) == 0.0:
            self.rate = 0.0
        else:
            self.rate = round(1.0 / self.factor, self.__class__.rate.digits[1])

    @fields.depends('rate')
    def on_change_rate(self):
        if (self.rate or 0.0) == 0.0:
            self.factor = 0.0
        else:
            self.factor = round(1.0 / self.rate,
                                self.__class__.factor.digits[1])

    @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,
            (cls._rec_name, ) + tuple(clause[1:]),
            ('symbol', ) + tuple(clause[1:]),
        ]

    @staticmethod
    def round(number, precision=1.0):
        i, d = divmod(precision, 1)
        base = round(number / precision)
        # Instead of multiply by the decimal part, we must divide by the
        # integer to avoid precision lost due to float
        return (base * i) + ((base / (1 / d)) if d != 0 else 0)

    @classmethod
    def validate(cls, uoms):
        super(Uom, cls).validate(uoms)
        for uom in uoms:
            uom.check_factor_and_rate()

    def check_factor_and_rate(self):
        "Check coherence between factor and rate"
        if self.rate == self.factor == 0.0:
            return
        if (self.rate != round(1.0 / self.factor,
                               self.__class__.rate.digits[1]) and self.factor
                != round(1.0 / self.rate, self.__class__.factor.digits[1])):
            self.raise_user_error('invalid_factor_and_rate', (self.rec_name, ))

    @classmethod
    def write(cls, *args):
        if Transaction().user == 0:
            super(Uom, cls).write(*args)
            return

        actions = iter(args)
        all_uoms = []
        for uoms, values in zip(actions, actions):
            if 'rate' not in values and 'factor' not in values \
                    and 'category' not in values:
                continue
            all_uoms += uoms

        old_uom = dict((uom.id, (uom.factor, uom.rate, uom.category.id))
                       for uom in all_uoms)

        super(Uom, cls).write(*args)

        for uom in all_uoms:
            if uom.factor != old_uom[uom.id][0] \
                    or uom.rate != old_uom[uom.id][1] \
                    or uom.category.id != old_uom[uom.id][2]:

                cls.raise_user_error('change_uom_rate_title',
                                     error_description='change_uom_rate')

    @property
    def accurate_field(self):
        """
        Select the more accurate field.
        It chooses the field that has the least decimal.
        """
        lengths = {}
        for field in ('rate', 'factor'):
            format = '%%.%df' % getattr(self.__class__, field).digits[1]
            lengths[field] = len(
                (format % getattr(self, field)).split('.')[1].rstrip('0'))
        if lengths['rate'] < lengths['factor']:
            return 'rate'
        elif lengths['factor'] < lengths['rate']:
            return 'factor'
        elif self.factor >= 1.0:
            return 'factor'
        else:
            return 'rate'

    @classmethod
    def compute_qty(cls, from_uom, qty, to_uom, round=True):
        """
        Convert quantity for given uom's.
        """
        if not qty or (from_uom is None and to_uom is None):
            return qty
        if from_uom is None:
            raise ValueError("missing from_uom")
        if to_uom is None:
            raise ValueError("missing to_uom")
        if from_uom.category.id != to_uom.category.id:
            raise ValueError("cannot convert between %s and %s" %
                             (from_uom.category.name, to_uom.category.name))

        if from_uom.accurate_field == 'factor':
            amount = qty * from_uom.factor
        else:
            amount = qty / from_uom.rate

        if to_uom.accurate_field == 'factor':
            amount = amount / to_uom.factor
        else:
            amount = amount * to_uom.rate

        if round:
            amount = cls.round(amount, to_uom.rounding)

        return amount

    @classmethod
    def compute_price(cls, from_uom, price, to_uom):
        """
        Convert price for given uom's.
        """
        if not price or (from_uom is None and to_uom is None):
            return price
        if from_uom is None:
            raise ValueError("missing from_uom")
        if to_uom is None:
            raise ValueError("missing to_uom")
        if from_uom.category.id != to_uom.category.id:
            raise ValueError('cannot convert between %s and %s' %
                             (from_uom.category.name, to_uom.category.name))

        factor_format = '%%.%df' % cls.factor.digits[1]
        rate_format = '%%.%df' % cls.rate.digits[1]

        if from_uom.accurate_field == 'factor':
            new_price = price / Decimal(factor_format % from_uom.factor)
        else:
            new_price = price * Decimal(rate_format % from_uom.rate)

        if to_uom.accurate_field == 'factor':
            new_price = new_price * Decimal(factor_format % to_uom.factor)
        else:
            new_price = new_price / Decimal(rate_format % to_uom.rate)

        return new_price
Exemple #13
0
class Lang(DeactivableMixin, ModelSQL, ModelView):
    "Language"
    __name__ = "ir.lang"
    name = fields.Char('Name', required=True, translate=True)
    code = fields.Char('Code',
                       required=True,
                       help="RFC 4646 tag: http://tools.ietf.org/html/rfc4646")
    translatable = fields.Boolean('Translatable', readonly=True)
    parent = fields.Char("Parent Code", help="Code of the exceptional parent")
    direction = fields.Selection([
        ('ltr', 'Left-to-right'),
        ('rtl', 'Right-to-left'),
    ],
                                 'Direction',
                                 required=True)

    # date
    date = fields.Char('Date', required=True)

    am = fields.Char("AM")
    pm = fields.Char("PM")

    # number
    grouping = fields.Char('Grouping', required=True)
    decimal_point = fields.Char('Decimal Separator', required=True)
    thousands_sep = fields.Char('Thousands Separator')

    # monetary formatting
    mon_grouping = fields.Char('Grouping', required=True)
    mon_decimal_point = fields.Char('Decimal Separator', required=True)
    mon_thousands_sep = fields.Char('Thousands Separator')
    p_sign_posn = fields.Integer('Positive Sign Position', required=True)
    n_sign_posn = fields.Integer('Negative Sign Position', required=True)
    positive_sign = fields.Char('Positive Sign')
    negative_sign = fields.Char('Negative Sign')
    p_cs_precedes = fields.Boolean('Positive Currency Symbol Precedes')
    n_cs_precedes = fields.Boolean('Negative Currency Symbol Precedes')
    p_sep_by_space = fields.Boolean('Positive Separate by Space')
    n_sep_by_space = fields.Boolean('Negative Separate by Space')

    _lang_cache = Cache('ir.lang')
    _code_cache = Cache('ir.lang.code', context=False)

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

        table = cls.__table__()
        cls._sql_constraints += [
            ('check_decimal_point_thousands_sep',
             Check(table, table.decimal_point != table.thousands_sep),
             'decimal_point and thousands_sep must be different!'),
        ]
        cls._buttons.update({
            'load_translations': {},
            'unload_translations': {
                'invisible': ~Eval('translatable', False),
            },
        })

    @classmethod
    def search_rec_name(cls, name, clause):
        langs = cls.search([('code', ) + tuple(clause[1:])], order=[])
        if langs:
            langs += cls.search([('name', ) + tuple(clause[1:])], order=[])
            return [('id', 'in', [l.id for l in langs])]
        return [('name', ) + tuple(clause[1:])]

    @classmethod
    def read(cls, ids, fields_names):
        pool = Pool()
        Translation = pool.get('ir.translation')
        Config = pool.get('ir.configuration')
        res = super(Lang, cls).read(ids, fields_names)
        if (Transaction().context.get('translate_name')
                and (not fields_names or 'name' in fields_names)):
            with Transaction().set_context(language=Config.get_language(),
                                           translate_name=False):
                res2 = cls.read(ids, ['id', 'code', 'name'])
            for record2 in res2:
                for record in res:
                    if record['id'] == record2['id']:
                        break
                res_trans = Translation.get_ids(cls.__name__ + ',name',
                                                'model', record2['code'],
                                                [record2['id']])
                record['name'] = (res_trans.get(record2['id'], False)
                                  or record2['name'])
        return res

    @staticmethod
    def default_translatable():
        return False

    @staticmethod
    def default_direction():
        return 'ltr'

    @staticmethod
    def default_date():
        return '%m/%d/%Y'

    @staticmethod
    def default_grouping():
        return '[]'

    @staticmethod
    def default_decimal_point():
        return '.'

    @staticmethod
    def default_thousands_sep():
        return ','

    @classmethod
    def default_mon_grouping(cls):
        return '[]'

    @classmethod
    def default_mon_thousands_sep(cls):
        return ','

    @classmethod
    def default_mon_decimal_point(cls):
        return '.'

    @classmethod
    def default_p_sign_posn(cls):
        return 1

    @classmethod
    def default_n_sign_posn(cls):
        return 1

    @classmethod
    def default_negative_sign(cls):
        return '-'

    @classmethod
    def default_positive_sign(cls):
        return ''

    @classmethod
    def default_p_cs_precedes(cls):
        return True

    @classmethod
    def default_n_cs_precedes(cls):
        return True

    @classmethod
    def default_p_sep_by_space(cls):
        return False

    @classmethod
    def default_n_sep_by_space(cls):
        return False

    @classmethod
    @ModelView.button
    def load_translations(cls, languages):
        pool = Pool()
        Module = pool.get('ir.module')
        codes = set()
        cls.write(languages, {'translatable': True})
        for language in languages:
            code = language.code
            while code:
                codes.add(code)
                code = get_parent_language(code)
        modules = Module.search([
            ('state', '=', 'activated'),
        ])
        modules = [m.name for m in modules]
        for node in create_graph(modules):
            load_translations(pool, node, codes)

    @classmethod
    @ModelView.button
    def unload_translations(cls, languages):
        pool = Pool()
        Translation = pool.get('ir.translation')
        cls.write(languages, {'translatable': False})
        languages = [l.code for l in languages]
        Translation.delete(
            Translation.search([
                ('lang', 'in', languages),
                ('module', '!=', None),
            ]))

    @classmethod
    def validate(cls, languages):
        super(Lang, cls).validate(languages)
        cls.check_grouping(languages)
        cls.check_date(languages)
        cls.check_translatable(languages)

    @classmethod
    def check_grouping(cls, langs):
        '''
        Check if grouping is list of numbers
        '''
        for lang in langs:
            for grouping in [lang.grouping, lang.mon_grouping]:
                try:
                    grouping = literal_eval(grouping)
                    for i in grouping:
                        if not isinstance(i, int):
                            raise
                except Exception:
                    raise GroupingError(
                        gettext('ir.msg_language_invalid_grouping',
                                grouping=grouping,
                                language=lang.rec_name))

    @classmethod
    def check_date(cls, langs):
        '''
        Check the date format
        '''
        for lang in langs:
            date = lang.date
            try:
                datetime.datetime.now().strftime(date)
            except Exception:
                raise DateError(
                    gettext('ir.msg_language_invalid_date',
                            format=lang.date,
                            language=lang.rec_name))
            if (('%Y' not in lang.date)
                    or ('%b' not in lang.date and '%B' not in lang.date
                        and '%m' not in lang.date and '%-m' not in lang.date)
                    or ('%d' not in lang.date and '%-d' not in lang.date
                        and '%j' not in lang.date and '%-j' not in lang.date)
                    or ('%x' in lang.date or '%X' in lang.date
                        or '%c' in lang.date or '%Z' in lang.date)):
                raise DateError(
                    gettext('ir.msg_language_invalid_date',
                            format=lang.date,
                            language=lang.rec_name))

    @classmethod
    def check_translatable(cls, langs):
        pool = Pool()
        Config = pool.get('ir.configuration')
        # Skip check for root because when languages are created from XML file,
        # translatable is not yet set.
        if Transaction().user == 0:
            return True
        for lang in langs:
            if (lang.code == Config.get_language() and not lang.translatable):
                raise TranslatableError(
                    gettext('ir.msg_language_default_translatable'))

    @staticmethod
    def check_xml_record(langs, values):
        return True

    @classmethod
    def get_translatable_languages(cls):
        res = cls._lang_cache.get('translatable_languages')
        if res is None:
            langs = cls.search([
                ('translatable', '=', True),
            ])
            res = [x.code for x in langs]
            cls._lang_cache.set('translatable_languages', res)
        return res

    @classmethod
    def create(cls, vlist):
        pool = Pool()
        Translation = pool.get('ir.translation')
        # Clear cache
        cls._lang_cache.clear()
        languages = super(Lang, cls).create(vlist)
        Translation._get_language_cache.clear()
        _parents.clear()
        return languages

    @classmethod
    def write(cls, langs, values, *args):
        pool = Pool()
        Translation = pool.get('ir.translation')
        # Clear cache
        cls._lang_cache.clear()
        cls._code_cache.clear()
        super(Lang, cls).write(langs, values, *args)
        Translation._get_language_cache.clear()
        _parents.clear()

    @classmethod
    def delete(cls, langs):
        pool = Pool()
        Config = pool.get('ir.configuration')
        Translation = pool.get('ir.translation')
        for lang in langs:
            if lang.code == Config.get_language():
                raise DeleteDefaultError(
                    gettext('ir.msg_language_delete_default'))
        # Clear cache
        cls._lang_cache.clear()
        cls._code_cache.clear()
        super(Lang, cls).delete(langs)
        Translation._get_language_cache.clear()
        _parents.clear()

    @classmethod
    def get(cls, code=None):
        "Return language instance for the code or the transaction language"
        if code is None:
            code = Transaction().language
        lang_id = cls._code_cache.get(code)
        if not lang_id:
            with Transaction().set_context(active_test=False):
                lang, = cls.search([
                    ('code', '=', code),
                ])
            cls._code_cache.set(code, lang.id)
        else:
            lang = cls(lang_id)
        return lang

    def _group(self, s, monetary=False):
        # Code from _group in locale.py

        # Iterate over grouping intervals
        def _grouping_intervals(grouping):
            last_interval = 0
            for interval in grouping:
                # if grouping is -1, we are done
                if interval == CHAR_MAX:
                    return
                # 0: re-use last group ad infinitum
                if interval == 0:
                    while True:
                        yield last_interval
                yield interval
                last_interval = interval

        if monetary:
            thousands_sep = self.mon_thousands_sep
            grouping = literal_eval(self.mon_grouping)
        else:
            thousands_sep = self.thousands_sep
            grouping = literal_eval(self.grouping)
        if not grouping:
            return (s, 0)
        if s[-1] == ' ':
            stripped = s.rstrip()
            right_spaces = s[len(stripped):]
            s = stripped
        else:
            right_spaces = ''
        left_spaces = ''
        groups = []
        for interval in _grouping_intervals(grouping):
            if not s or s[-1] not in "0123456789":
                # only non-digit characters remain (sign, spaces)
                left_spaces = s
                s = ''
                break
            groups.append(s[-interval:])
            s = s[:-interval]
        if s:
            groups.append(s)
        groups.reverse()
        return (left_spaces + thousands_sep.join(groups) + right_spaces,
                len(thousands_sep) * (len(groups) - 1))

    def format(self,
               percent,
               value,
               grouping=False,
               monetary=False,
               *additional):
        '''
        Returns the lang-aware substitution of a %? specifier (percent).
        '''

        # Code from format in locale.py

        # Strip a given amount of excess padding from the given string
        def _strip_padding(s, amount):
            lpos = 0
            while amount and s[lpos] == ' ':
                lpos += 1
                amount -= 1
            rpos = len(s) - 1
            while amount and s[rpos] == ' ':
                rpos -= 1
                amount -= 1
            return s[lpos:rpos + 1]

        # this is only for one-percent-specifier strings
        # and this should be checked
        if percent[0] != '%':
            raise ValueError("format() must be given exactly one %char "
                             "format specifier")
        if additional:
            formatted = percent % ((value, ) + additional)
        else:
            formatted = percent % value
        # floats and decimal ints need special action!
        if percent[-1] in 'eEfFgG':
            seps = 0
            parts = formatted.split('.')
            if grouping:
                parts[0], seps = self._group(parts[0], monetary=monetary)
            if monetary:
                decimal_point = self.mon_decimal_point
            else:
                decimal_point = self.decimal_point
            formatted = decimal_point.join(parts)
            if seps:
                formatted = _strip_padding(formatted, seps)
        elif percent[-1] in 'diu':
            seps = 0
            if grouping:
                formatted, seps = self._group(formatted, monetary=monetary)
            if seps:
                formatted = _strip_padding(formatted, seps)
        return formatted

    def currency(self,
                 val,
                 currency,
                 symbol=True,
                 grouping=False,
                 digits=None):
        """
        Formats val according to the currency settings in lang.
        """
        # Code from currency in locale.py

        # check for illegal values
        if digits is None:
            digits = currency.digits
        if digits == 127:
            raise ValueError("Currency formatting is not possible using "
                             "the 'C' locale.")

        s = self.format('%%.%if' % digits, abs(val), grouping, monetary=True)
        # '<' and '>' are markers if the sign must be inserted
        # between symbol and value
        s = '<' + s + '>'

        if symbol:
            smb = currency.symbol
            precedes = (val < 0 and self.n_cs_precedes or self.p_cs_precedes)
            separated = (val < 0 and self.n_sep_by_space
                         or self.p_sep_by_space)

            if precedes:
                s = smb + (separated and ' ' or '') + s
            else:
                s = s + (separated and ' ' or '') + smb

        sign_pos = val < 0 and self.n_sign_posn or self.p_sign_posn
        sign = val < 0 and self.negative_sign or self.positive_sign

        if sign_pos == 0:
            s = '(' + s + ')'
        elif sign_pos == 1:
            s = sign + s
        elif sign_pos == 2:
            s = s + sign
        elif sign_pos == 3:
            s = s.replace('<', sign)
        elif sign_pos == 4:
            s = s.replace('>', sign)
        else:
            # the default if nothing specified;
            # this should be the most fitting sign position
            s = sign + s

        return s.replace('<', '').replace('>', '')

    def strftime(self, value, format=None):
        '''
        Convert value to a string as specified by the format argument.
        '''
        pool = Pool()
        Month = pool.get('ir.calendar.month')
        Day = pool.get('ir.calendar.day')
        if format is None:
            format = self.date
            if isinstance(value, datetime.datetime):
                format += ' %H:%M:%S'
        format = format.replace('%x', self.date)
        format = format.replace('%X', '%H:%M:%S')
        if isinstance(value, datetime.date):
            for f, i, klass in (('%A', 6, Day), ('%B', 1, Month)):
                for field, f in [('name', f), ('abbreviation', f.lower())]:
                    locale = klass.locale(self, field=field)
                    format = format.replace(f, locale[value.timetuple()[i]])
        if isinstance(value, datetime.time):
            time = value
        else:
            try:
                time = value.time()
            except AttributeError:
                time = None
        if time:
            if time < datetime.time(12):
                p = self.am or 'AM'
            else:
                p = self.pm or 'PM'
            format = format.replace('%p', p)
        return value.strftime(format)
Exemple #14
0
class Address(ModelSQL, ModelView):
    "Address"
    __name__ = 'party.address'
    party = fields.Many2One('party.party',
                            'Party',
                            required=True,
                            ondelete='CASCADE',
                            select=True,
                            states={
                                'readonly':
                                If(~Eval('active'), True,
                                   Eval('id', 0) > 0),
                            },
                            depends=['active', 'id'])
    name = fields.Char('Name', states=STATES, depends=DEPENDS)
    street = fields.Char('Street', states=STATES, depends=DEPENDS)
    streetbis = fields.Char('Street (bis)', states=STATES, depends=DEPENDS)
    zip = fields.Char('Zip', states=STATES, depends=DEPENDS)
    city = fields.Char('City', states=STATES, depends=DEPENDS)
    country = fields.Many2One('country.country',
                              'Country',
                              states=STATES,
                              depends=DEPENDS)
    subdivision = fields.Many2One("country.subdivision",
                                  'Subdivision',
                                  domain=[('country', '=', Eval('country'))],
                                  states=STATES,
                                  depends=['active', 'country'])
    active = fields.Boolean('Active')
    sequence = fields.Integer("Sequence")
    full_address = fields.Function(fields.Text('Full Address'),
                                   'get_full_address')

    @classmethod
    def __setup__(cls):
        super(Address, cls).__setup__()
        cls._order.insert(0, ('party', 'ASC'))
        cls._order.insert(1, ('sequence', 'ASC'))
        cls._error_messages.update({
            'write_party':
            'You can not modify the party of address "%s".',
        })

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

        super(Address, cls).__register__(module_name)

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

    @staticmethod
    def order_sequence(tables):
        table, _ = tables[None]
        return [table.sequence == None, table.sequence]

    @staticmethod
    def default_active():
        return True

    _autocomplete_limit = 100

    def _autocomplete_domain(self):
        domain = []
        if self.country:
            domain.append(('country', '=', self.country.id))
        if self.subdivision:
            domain.append([
                'OR',
                ('subdivision', '=', self.subdivision.id),
                ('subdivision', '=', None),
            ])
        return domain

    def _autocomplete_search(self, domain, name):
        pool = Pool()
        Zip = pool.get('country.zip')
        if domain:
            records = Zip.search(domain, limit=self._autocomplete_limit)
            if len(records) < self._autocomplete_limit:
                return sorted({getattr(z, name) for z in records})
        return []

    @fields.depends('city', 'country', 'subdivision')
    def autocomplete_zip(self):
        domain = self._autocomplete_domain()
        if self.city:
            domain.append(('city', 'ilike', '%%%s%%' % self.city))
        return self._autocomplete_search(domain, 'zip')

    @fields.depends('zip', 'country', 'subdivision')
    def autocomplete_city(self):
        domain = self._autocomplete_domain()
        if self.zip:
            domain.append(('zip', 'ilike', '%s%%' % self.zip))
        return self._autocomplete_search(domain, 'city')

    def get_full_address(self, name):
        full_address = ''
        if self.name:
            full_address = self.name
        if self.street:
            if full_address:
                full_address += '\n'
            full_address += self.street
        if self.streetbis:
            if full_address:
                full_address += '\n'
            full_address += self.streetbis
        if self.zip or self.city:
            if full_address:
                full_address += '\n'
            if self.zip:
                full_address += self.zip
            if self.city:
                if full_address[-1:] != '\n':
                    full_address += ' '
                full_address += self.city
        if self.country or self.subdivision:
            if full_address:
                full_address += '\n'
            if self.subdivision:
                full_address += self.subdivision.name
            if self.country:
                if full_address[-1:] != '\n':
                    full_address += ' '
                full_address += self.country.name
        return full_address

    def get_rec_name(self, name):
        return ", ".join(
            x for x in [self.name, self.party.rec_name, self.zip, self.city]
            if x)

    @classmethod
    def search_rec_name(cls, name, clause):
        return [
            'OR',
            ('zip', ) + tuple(clause[1:]),
            ('city', ) + tuple(clause[1:]),
            ('name', ) + tuple(clause[1:]),
            ('party', ) + tuple(clause[1:]),
        ]

    @classmethod
    def write(cls, *args):
        actions = iter(args)
        for addresses, values in zip(actions, actions):
            if 'party' in values:
                for address in addresses:
                    if address.party.id != values['party']:
                        cls.raise_user_error('write_party',
                                             (address.rec_name, ))
        super(Address, cls).write(*args)

    @fields.depends('subdivision', 'country')
    def on_change_country(self):
        if (self.subdivision and self.subdivision.country != self.country):
            return {'subdivision': None}
        return {}
Exemple #15
0
class Work(metaclass=PoolMeta):
    __name__ = 'project.work'
    product = fields.Many2One('product.product', 'Product',
        domain=[
            ('type', '=', 'service'),
            ('default_uom_category', '=', Id('product', 'uom_cat_time')),
            ])
    list_price = fields.Numeric('List Price', digits=price_digits)
    revenue = fields.Function(fields.Numeric('Revenue',
            digits=(16, Eval('currency_digits', 2)),
            depends=['currency_digits']), 'get_total')
    cost = fields.Function(fields.Numeric('Cost',
            digits=(16, Eval('currency_digits', 2)),
            depends=['currency_digits']), 'get_total')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
        'on_change_with_currency_digits')

    @classmethod
    def __setup__(cls):
        pool = Pool()

        super(Work, cls).__setup__()

        try:
            pool.get('purchase.line')
        except KeyError:
            pass
        else:
            # Add purchase lines if purchase is installed
            cls.purchase_lines = fields.One2Many('purchase.line', 'work',
                'Purchase Lines', domain=[
                    ('purchase.company', '=', Eval('company', -1)),
                    ('type', '=', 'line'),
                    ],
                depends=['company'])

    @classmethod
    def _get_cost(cls, works):
        pool = Pool()
        Line = pool.get('timesheet.line')
        Work = pool.get('timesheet.work')
        transaction = Transaction()
        cursor = transaction.connection.cursor()

        costs = dict.fromkeys([w.id for w in works], 0)

        table = cls.__table__()
        work = Work.__table__()
        line = Line.__table__()

        # Timesheet cost
        work_ids = [w.id for w in works]
        for sub_ids in grouped_slice(work_ids):
            red_sql = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.join(work,
                    condition=(
                        Concat(cls.__name__ + ',', table.id) == work.origin)
                    ).join(line, condition=line.work == work.id
                    ).select(table.id, Sum(line.cost_price * line.duration),
                    where=red_sql,
                    group_by=[table.id]))
            for work_id, cost in cursor.fetchall():
                # SQLite stores timedelta as float
                if not isinstance(cost, float):
                    cost = cost.total_seconds()
                # Convert from seconds
                cost /= 60 * 60
                costs[work_id] += Decimal(str(cost))

        # Purchase cost
        if hasattr(cls, 'purchase_lines'):
            for work_id, cost in cls._purchase_cost(works).items():
                costs[work_id] += cost

        for work in works:
            costs[work.id] = work.company.currency.round(costs[work.id])
        return costs

    @classmethod
    def _purchase_cost(cls, works):
        'Compute direct purchase cost'
        pool = Pool()
        Currency = pool.get('currency.currency')
        PurchaseLine = pool.get('purchase.line')
        InvoiceLine = pool.get('account.invoice.line')
        Invoice = pool.get('account.invoice')
        Company = pool.get('company.company')

        cursor = Transaction().connection.cursor()
        table = cls.__table__()
        purchase_line = PurchaseLine.__table__()
        invoice_line = InvoiceLine.__table__()
        invoice = Invoice.__table__()
        company = Company.__table__()

        amounts = defaultdict(Decimal)
        work_ids = [w.id for w in works]
        work2currency = {}
        iline2work = {}
        for sub_ids in grouped_slice(work_ids):
            where = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.join(purchase_line,
                    condition=purchase_line.work == table.id
                    ).join(invoice_line,
                    condition=invoice_line.origin == Concat(
                        'purchase.line,', purchase_line.id)
                    ).join(invoice,
                    condition=invoice_line.invoice == invoice.id
                    ).select(invoice_line.id, table.id,
                    where=where & ~invoice.state.in_(['draft', 'cancel'])))
            iline2work.update(cursor.fetchall())

            cursor.execute(*table.join(company,
                    condition=table.company == company.id
                    ).select(table.id, company.currency,
                    where=where))
            work2currency.update(cursor.fetchall())

        currencies = Currency.browse(set(work2currency.values()))
        id2currency = {c.id: c for c in currencies}

        invoice_lines = InvoiceLine.browse(list(iline2work.keys()))
        for invoice_line in invoice_lines:
            invoice = invoice_line.invoice
            work_id = iline2work[invoice_line.id]
            currency_id = work2currency[work_id]
            currency = id2currency[currency_id]
            if currency != invoice.currency:
                with Transaction().set_context(date=invoice.currency_date):
                    amount = Currency.compute(invoice.currency,
                        invoice_line.amount, currency)
            else:
                amount = invoice_line.amount
            amounts[work_id] += amount
        return amounts

    @classmethod
    def _get_revenue(cls, works):
        revenues = {}
        for work in works:
            if work.list_price:
                revenues[work.id] = work.company.currency.round(
                    work.list_price * Decimal(str(work.effort_hours)))
            else:
                revenues[work.id] = Decimal(0)
        return revenues

    @fields.depends('company')
    def on_change_with_currency_digits(self, name=None):
        if self.company:
            return self.company.currency.digits
        return 2

    @staticmethod
    def default_currency_digits():
        Company = Pool().get('company.company')
        company = Transaction().context.get('company')
        if company:
            company = Company(company)
            return company.currency.digits
        return 2

    @fields.depends('product', 'company')
    def on_change_product(self):
        pool = Pool()
        User = pool.get('res.user')
        ModelData = pool.get('ir.model.data')
        Uom = pool.get('product.uom')
        Currency = pool.get('currency.currency')

        if not self.product:
            return

        hour_uom = Uom(ModelData.get_id('product', 'uom_hour'))
        self.list_price = Uom.compute_price(self.product.default_uom,
            self.product.list_price, hour_uom)

        if self.company:
            user = User(Transaction().user)
            if user.company != self.company:
                if user.company.currency != self.company.currency:
                    self.list_price = Currency.compute(user.company.currency,
                        self.list_price, self.company.currency, round=False)

        digits = self.__class__.list_price.digits
        self.list_price = self.list_price.quantize(
            Decimal(str(10.0 ** -digits[1])))
Exemple #16
0
class Integer(ModelSQL):
    'Integer'
    __name__ = 'test.integer'
    integer = fields.Integer(string='Integer',
                             help='Test integer',
                             required=False)
Exemple #17
0
class NullOrder(ModelSQL):
    "Null Order"
    __name__ = 'test.modelsql.null_order'
    integer = fields.Integer('Integer')
Exemple #18
0
class View(ModelSQL, ModelView):
    "View"
    __name__ = 'ir.ui.view'
    _rec_name = 'model'
    model = fields.Char('Model', select=True, states={
            'required': Eval('type').in_([None, 'tree', 'form', 'graph']),
            })
    priority = fields.Integer('Priority', required=True, select=True)
    type = fields.Selection([
            (None, ''),
            ('tree', 'Tree'),
            ('form', 'Form'),
            ('graph', 'Graph'),
            ('calendar', 'Calendar'),
            ('board', 'Board'),
            ], 'View Type', select=True,
        domain=[
            If(Bool(Eval('inherit')),
                ('type', '=', None),
                ('type', '!=', None)),
            ],
        depends=['inherit'])
    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'])

    @classmethod
    def __setup__(cls):
        super(View, cls).__setup__()
        cls._error_messages.update({
                'invalid_xml': 'Invalid XML for view "%s".',
                })
        cls._order.insert(0, ('priority', 'ASC'))
        cls._buttons.update({
                'show': {
                    'readonly': Eval('type') != 'form',
                    },
                })

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

        # Migration from 2.4 arch moved into data
        if table.column_exist('arch'):
            table.column_rename('arch', 'data')

        super(View, cls).__register__(module_name)

        # New instance to refresh definition
        table = TableHandler(cls, module_name)

        # Migration from 1.0 arch no more required
        table.not_null_action('arch', action='remove')

        # Migration from 2.4 model no more required
        table.not_null_action('model', action='remove')

    @staticmethod
    def default_priority():
        return 16

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

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

    @classmethod
    @memoize(10)
    def get_rng(cls, type_):
        if sys.version_info < (3,):
            filename = __file__.decode(sys.getfilesystemencoding())
        else:
            filename = __file__
        rng_name = os.path.join(os.path.dirname(filename), type_ + '.rng')
        with open(rng_name, 'rb') as fp:
            rng = etree.fromstring(fp.read())
        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:
                tree = etree.fromstring(xml)
            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()))
                    logger.error('Invalid XML view %s:\n%s\n%s',
                        view.rec_name, error_log, xml)
                    cls.raise_user_error(
                        'invalid_xml', (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, e:
                        error_log = '%s: <%s %s="%s"/>' % (
                            e, element.get('id') or element.get('name'), attr,
                            element.get(attr))
                        logger.error(
                            'Invalid XML view %s:\n%s\n%s',
                            view.rec_name, error_log, xml)
                        cls.raise_user_error(
                            'invalid_xml', (view.rec_name,), error_log)
                for child in element:
                    encode(child)
            encode(root_element)
Exemple #19
0
class ShipmentIn:
    __name__ = 'stock.shipment.in'
    carrier = fields.Many2One('carrier',
                              'Carrier',
                              states={
                                  'readonly': Eval('state') != 'draft',
                              },
                              depends=['state'])
    cost_currency = fields.Many2One(
        'currency.currency',
        'Cost Currency',
        states={
            'required': Bool(Eval('cost')),
            'readonly': ~Eval('state').in_(['draft', 'assigned', 'packed']),
        },
        depends=['cost', 'state'])
    cost_currency_digits = fields.Function(
        fields.Integer('Cost Currency Digits'),
        'on_change_with_cost_currency_digits')
    cost = fields.Numeric(
        'Cost',
        digits=(16, Eval('cost_currency_digits', 2)),
        states={
            'readonly': ~Eval('state').in_(['draft', 'assigned', 'packed']),
        },
        depends=['carrier', 'state', 'cost_currency_digits'])

    @fields.depends('currency')
    def on_change_with_cost_currency_digits(self, name=None):
        if self.cost_currency:
            return self.cost_currency.digits
        return 2

    def _get_carrier_context(self):
        return {}

    @fields.depends(methods=['incoming_moves'])
    def on_change_carrier(self):
        self.on_change_incoming_moves()

    @fields.depends('carrier', 'incoming_moves')
    def on_change_incoming_moves(self):
        try:
            super(ShipmentIn, self).on_change_incoming_moves()
        except AttributeError:
            pass
        if not self.carrier:
            return
        with Transaction().set_context(self._get_carrier_context()):
            cost, currency_id = self.carrier.get_purchase_price()
        self.cost = cost
        self.cost_currency = currency_id
        if self.cost_currency:
            self.cost_currency_digits = self.cost_currency.digits
        else:
            self.cost_currency_digits = 2

    def allocate_cost_by_value(self):
        pool = Pool()
        Currency = pool.get('currency.currency')
        Move = pool.get('stock.move')

        if not self.cost:
            return

        cost = Currency.compute(self.cost_currency,
                                self.cost,
                                self.company.currency,
                                round=False)
        moves = [
            m for m in self.incoming_moves if m.state not in ('done', 'cancel')
        ]

        sum_value = 0
        unit_prices = {}
        for move in moves:
            unit_price = Currency.compute(move.currency,
                                          move.unit_price,
                                          self.company.currency,
                                          round=False)
            unit_prices[move.id] = unit_price
            sum_value += unit_price * Decimal(str(move.quantity))

        costs = []
        digit = Move.unit_price.digits[1]
        exp = Decimal(str(10.0**-digit))
        difference = cost
        for move in moves:
            quantity = Decimal(str(move.quantity))
            if not sum_value:
                move_cost = cost / Decimal(len(moves))
            else:
                move_cost = cost * quantity * unit_prices[move.id] / sum_value
            unit_shipment_cost = (move_cost / quantity).quantize(
                exp, rounding=ROUND_DOWN)
            costs.append({
                'unit_shipment_cost':
                unit_shipment_cost,
                'difference':
                move_cost - (unit_shipment_cost * quantity),
                'move':
                move,
            })
            difference -= unit_shipment_cost * quantity
        costs.sort(key=itemgetter('difference'))
        for cost in costs:
            move = cost['move']
            quantity = Decimal(str(move.quantity))
            if exp * quantity < difference:
                cost['unit_shipment_cost'] += exp
                difference -= exp * quantity
            if difference < exp:
                break

        for cost in costs:
            move = cost['move']
            unit_shipment_cost = Currency.compute(self.company.currency,
                                                  cost['unit_shipment_cost'],
                                                  move.currency,
                                                  round=False)
            unit_shipment_cost = unit_shipment_cost.quantize(
                exp, rounding=ROUND_HALF_EVEN)
            Move.write(
                [move], {
                    'unit_price': move.unit_price + unit_shipment_cost,
                    'unit_shipment_cost': unit_shipment_cost,
                })

    @classmethod
    @ModelView.button
    @Workflow.transition('received')
    def receive(cls, shipments):
        Carrier = Pool().get('carrier')
        for shipment in shipments:
            if shipment.carrier:
                allocation_method = \
                    shipment.carrier.carrier_cost_allocation_method
            else:
                allocation_method = \
                    Carrier.default_carrier_cost_allocation_method()
            getattr(shipment, 'allocate_cost_by_%s' % allocation_method)()
        super(ShipmentIn, cls).receive(shipments)
class AvailableMedicine(ModelSQL, ModelView):
    "AvailableMedicine"
    __name__ = "hrp_inventory.available_medicine"

    warehouse = fields.Many2One('stock.location',
                                'Warehouse',
                                domain=[('type', '=', 'warehouse')],
                                select=True)
    varieties_num = fields.Integer('Varieties Num', readonly=True)
    lines = fields.One2Many('hrp_inventory.available_medicine_line',
                            'available_medicine', 'Lines')

    @classmethod
    def __setup__(cls):
        super(AvailableMedicine, cls).__setup__()
        cls._buttons.update({
            'create_the_inventory': {
                'readonly': Eval('lines') != '',
            },
        })

    @classmethod
    @ModelView.button
    def create_the_inventory(self, available_medicine):
        Product = Pool().get('product.product')
        Date = Pool().get('ir.date')
        AvailableMedicineLine = Pool().get(
            'hrp_inventory.available_medicine_line')
        with Transaction().set_context(stock_date_end=Date.today()):
            warehouse_pbl = Product.products_by_location(
                [available_medicine[0].warehouse.storage_location],
                with_childs=True)
            lines = 0
            for key, value in warehouse_pbl.iteritems():
                lines += 1
                product = Product.search([('id', '=', key[1])])
                if not product:
                    continue
                to_create = {
                    u'lines':
                    lines,
                    u'available_medicine':
                    available_medicine[0].id,
                    u'product':
                    product[0].id,
                    u'product_code':
                    product[0].code.encode('utf-8'),
                    u'product_name':
                    product[0].template.name.encode('utf-8'),
                    u'scattered_uom':
                    product[0].default_uom.id,
                    u'manufacturers_code':
                    product[0].template.manufacturers_code.encode('utf-8'),
                    u'manufacturers':
                    product[0].template.manufacturers_describtion.encode(
                        'utf-8'),
                    u'drug_specifications':
                    product[0].template.drug_specifications,
                }
                AvailableMedicineLine.create([to_create])
            AvailableMedicine.write(available_medicine,
                                    {'varieties_num': lines})
Exemple #21
0
class Origin(origin_mixin(_states), ModelSQL, ModelView):
    "Account Statement Origin"
    __name__ = 'account.statement.origin'
    _rec_name = 'number'

    lines = fields.One2Many(
        'account.statement.line', 'origin', "Lines",
        states={
            'readonly': ((Eval('statement_id', -1) < 0)
                | ~Eval('statement_state').in_(['draft', 'validated'])),
            },
        domain=[
            ('statement', '=', Eval('statement', -1)),
            ('date', '=', Eval('date', None)),
            ])
    statement_id = fields.Function(
        fields.Integer("Statement ID"), 'on_change_with_statement_id')
    pending_amount = fields.Function(Monetary(
            "Pending Amount", currency='currency', digits='currency'),
        'on_change_with_pending_amount', searcher='search_pending_amount')
    information = fields.Dict(
        'account.statement.origin.information', "Information", readonly=True)

    @classmethod
    def __register__(cls, module_name):
        table = cls.__table_handler__(module_name)

        # Migration from 5.0: rename informations into information
        table.column_rename('informations', 'information')

        super(Origin, cls).__register__(module_name)

    @fields.depends('statement', '_parent_statement.id')
    def on_change_with_statement_id(self, name=None):
        if self.statement:
            return self.statement.id
        return -1

    @fields.depends('lines', 'amount')
    def on_change_with_pending_amount(self, name=None):
        lines_amount = sum(
            getattr(l, 'amount') or Decimal(0) for l in self.lines)
        return (self.amount or Decimal(0)) - lines_amount

    @classmethod
    def search_pending_amount(cls, name, clause):
        pool = Pool()
        Line = pool.get('account.statement.line')
        table = cls.__table__()
        line = Line.__table__()

        _, operator, value = clause
        Operator = fields.SQL_OPERATORS[operator]

        query = (table.join(line, 'LEFT', condition=line.origin == table.id)
            .select(table.id,
                having=Operator(
                    table.amount - Coalesce(Sum(line.amount), 0), value),
                group_by=table.id))
        return [('id', 'in', query)]

    @classmethod
    def copy(cls, origins, default=None):
        default = default.copy() if default is not None else {}
        default.setdefault('lines')
        return super().copy(origins, default=default)
class AvailableMedicineLine(ModelSQL, ModelView):
    "AvailableMedicineLine"
    __name__ = "hrp_inventory.available_medicine_line"

    shelves_code = fields.Char('shelves_code',
                               select=True,
                               readonly=True,
                               required=False)
    new_shelves_code = fields.Char('new_shelves_code',
                                   select=True,
                                   readonly=False,
                                   required=False)
    warehouse = fields.Many2One('stock.location',
                                'Warehouse',
                                domain=[('type', '=', 'warehouse')],
                                select=True)
    product_code = fields.Function(
        fields.Char('product_code', readonly=True, required=False),
        'get_product_code')
    product_name = fields.Function(
        fields.Char('product_name', readonly=True, required=False),
        'get_product_name')
    product = fields.Many2One('product.product',
                              'Product',
                              domain=[('id', 'in', Eval('product_ids'))],
                              depends=['product_ids'],
                              select=True,
                              states={'readonly': Bool(Eval('product'))})
    product_ids = fields.Function(
        fields.One2Many('product.product', None, 'Products'),
        'on_change_with_product_ids')
    safety_stock = fields.Integer('Safety stock', required=False)
    scattered_uom = fields.Many2One('product.uom',
                                    'Default UOM',
                                    domain=[('category', '=',
                                             Eval('product_uom_category'))],
                                    required=False,
                                    depends=['product_uom_category'])
    product_uom_category = fields.Function(
        fields.Many2One('product.uom.category', 'Product Uom Category'),
        'on_change_with_product_uom_category')
    drug_specifications = fields.Function(
        fields.Char('Drug Speic', readonly=True, required=False),
        'get_drug_specifications')
    manufacturers_code = fields.Function(
        fields.Char('Manufacturers_code', readonly=True, required=False),
        'get_manufacturers_code')
    manufacturers = fields.Function(
        fields.Char('Manufacturers',
                    select=True,
                    readonly=True,
                    required=False), 'get_manufacturers')

    @staticmethod
    def default_warehouse():
        UserId = Pool().get('hrp_internal_delivery.test_straight')
        return UserId.get_user_warehouse()

    def get_product_code(self, name):

        Product = Pool().get('product.product')
        try:
            product = Product.search([('id', '=', self.product.id)])
            product_code = product[0].code.encode('utf-8')
        except:
            return None
        return product_code

    def get_product_name(self, name):

        Product = Pool().get('product.product')
        try:
            product = Product.search([('id', '=', self.product.id)])
            product_name = product[0].name.encode('utf-8')
        except:
            return None
        return product_name

    def get_drug_specifications(self, name):

        Product = Pool().get('product.product')
        try:
            product = Product.search([('id', '=', self.product.id)])
            drug_specifications = product[0].drug_specifications
        except:
            return None
        return drug_specifications

    def get_manufacturers_code(self, name):

        Product = Pool().get('product.product')
        try:
            product = Product.search([('id', '=', self.product.id)])
            manufacturers_code = product[0].manufacturers_code.encode('utf-8')
        except:
            return None
        return manufacturers_code

    def get_manufacturers(self, name):

        Product = Pool().get('product.product')
        try:
            product = Product.search([('id', '=', self.product.id)])
            manufacturers = product[0].manufacturers_describtion.encode(
                'utf-8')
        except:
            return None
        return manufacturers

    @fields.depends('warehouse')
    def on_change_with_product_ids(self, name=None):
        Config = Pool().get('purchase.configuration')
        config = Config(1)
        OrderPoint = Pool().get('stock.order_point')
        if self.warehouse == config.warehouse.id:
            order_point = OrderPoint.search([('warehouse_location', '=',
                                              self.warehouse)])
        else:
            order_point = OrderPoint.search([('secondary', '=', self.warehouse)
                                             ])
        product_ids = []
        for ids in order_point:
            product_ids.append(ids.product.id)
        return product_ids

    @fields.depends('product')
    def on_change_with_product_uom_category(self, name=None):
        if self.product:
            return self.product.default_uom_category.id

    @fields.depends('product', 'product_code', 'product_name', 'scattered_uom',
                    'drug_specifications', 'manufacturers_code',
                    'manufacturers')
    def on_change_product(self, name=None):
        if self.product:
            self.product_code = self.product.code
            self.product_name = self.product.name
            self.scattered_uom = self.product.template.default_uom
            self.drug_specifications = self.product.template.drug_specifications
            self.manufacturers_code = self.product.template.manufacturers_code
            self.manufacturers = self.product.template.manufacturers_describtion

    @classmethod
    def write(cls, records, values, *args):
        for i in records:
            try:
                values['shelves_code'] = values['new_shelves_code']
                values.pop('new_shelves_code')
            except:
                pass
        return super(AvailableMedicineLine, cls).write(records, values)

    @classmethod
    def create(cls, vlist):
        Product = Pool().get('product.product')
        ShelvesName = Pool().get('hrp_inventory.shelves_code')
        for value in vlist:
            if value['product'] and value['warehouse']:
                availble = AvailableMedicineLine.search([
                    ('product', '=', value['product']),
                    ('warehouse', '=', value['warehouse'])
                ])
            if value['product']:
                product = Product.search([('id', '=', value['product'])])
                value['scattered_uom'] = product[0].template.default_uom.id

            if value['new_shelves_code']:
                shelves_code = ShelvesName.search([
                    ('name', '=', value['new_shelves_code'])
                ])
                if not shelves_code:
                    ShelvesName.create([{'name': value['new_shelves_code']}])
            if not availble:
                try:
                    value['shelves_code'] = value['new_shelves_code']
                    value.pop('new_shelves_code')
                except:
                    pass
            else:
                cls.raise_user_error(u'该表中药品必须唯一,标识为:%s' % availble[0].id)
        return super(AvailableMedicineLine, cls).create(vlist)
Exemple #23
0
class ComputerLoan(Workflow, ModelSQL, ModelView):
    'Computer Loan'
    __name__ = 'computer.loan'

    salary_code = fields.Char('Salary Code',
                              states={
                                  'readonly': ~Eval('state').in_(['draft']),
                              },
                              depends=['state'])
    employee = fields.Many2One('company.employee',
                               'Name of Applicant',
                               states={
                                   'readonly': ~Eval('state').in_(['draft']),
                               },
                               depends=['state'])
    designation = fields.Many2One("employee.designation",
                                  "Applicant's Designation",
                                  states={
                                      'readonly':
                                      ~Eval('state').in_(['draft']),
                                  },
                                  depends=['state'])
    department = fields.Many2One('company.department', 'Department')
    pay_in_band = fields.Char('Pay in the Pay Band')
    price = fields.Float('Price of Personal Computer')
    amount_required = fields.Float("Amount Required",
                                   states={
                                       'readonly':
                                       ~Eval('state').in_(['draft']),
                                   },
                                   depends=['state'],
                                   required=True)
    date_of_retirement = fields.Date('Date of Retirement')
    dob = fields.Date("Date of Birth",
                      states={
                          'readonly': ~Eval('state').in_(['draft']),
                      },
                      depends=['state'])
    installment_no = fields.Integer("Number of installment")
    purpose = fields.Selection([
        ('', 'None'),
        ('yes', 'Yes'),
        ('no', 'No'),
    ],
                               string='Purpose',
                               states={
                                   'readonly': ~Eval('state').in_(['draft']),
                               },
                               depends=['state'],
                               required=True)
    drawal_date = fields.Date('Date of drawal of advance',
                              states={
                                  'invisible': ~Eval('purpose').in_(['yes']),
                                  'required': Eval('purpose').in_(['yes']),
                              },
                              depends=['purpose'])
    interest = fields.Float('Interest',
                            states={
                                'invisible': ~Eval('purpose').in_(['yes']),
                                'required': Eval('purpose').in_(['yes']),
                            },
                            depends=['purpose'])
    basic_pay = fields.Float("Basic Pay",
                             states={
                                 'readonly': ~Eval('state').in_(['draft']),
                             },
                             depends=['state'])
    cancel_reason = fields.Many2One(
        'loan.cancel.reason',
        'Cancel Reason',
        states={
            'invisible': ~Eval('state').in_(['forwarded_to_ao', 'cancel']),
            'readonly': ~Eval('state').in_(['forwarded_to_ao']),
        },
        depends=['state'],
    )
    payout = fields.Float('Payout',
                          states={
                              'readonly': ~Eval('state').in_(['draft']),
                              'invisible': ~Eval('refund').in_(['refundable']),
                          },
                          depends=['state'])
    pending = fields.Float('Pending',
                           states={
                               'readonly': ~Eval('state').in_(['draft']),
                               'invisible':
                               ~Eval('refund').in_(['refundable']),
                           },
                           depends=['state'])
    reschedule = fields.Float('Reschedule',
                              states={
                                  'readonly': ~Eval('state').in_(['draft']),
                                  'invisible':
                                  ~Eval('refund').in_(['refundable']),
                              },
                              depends=['state'])
    state = fields.Selection([('draft', 'Draft'),
                              ('forwarded_to_jo', 'Forwarded to JO'),
                              ('forwarded_to_ao', 'Forwarded to AO'),
                              ('approve', 'Approved'), ('cancel', 'Cancel')],
                             'Status',
                             readonly=True)

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls._transitions |= set((
            ('draft', 'forwarded_to_jo'),
            ('forwarded_to_jo', 'forwarded_to_ao'),
            ('forwarded_to_ao', 'approve'),
            ('forwarded_to_ao', 'cancel'),
        ))
        cls._buttons.update({
            'submitted_to_ao': {
                'invisible': ~Eval('state').in_(['draft']),
                'depends': ['state'],
            },
            'forward_to_jo': {
                'invisible': ~Eval('state').in_(['forwarded_to_jo']),
                'depends': ['state'],
            },
            'forward_to_ao': {
                'invisible': ~Eval('state').in_(['forwarded_to_ao']),
                'depends': ['state'],
            },
            'cancel': {
                'invisible': ~Eval('state').in_(['forwarded_to_ao']),
                'depends': ['state'],
            },
        })

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    @ModelView.button
    @Workflow.transition('forwarded_to_jo')
    def submitted_to_ao(cls, records):
        cls.loan_installment(records)
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('forwarded_to_ao')
    def forward_to_jo(cls, records):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('approve')
    def forward_to_ao(cls, records):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, records):
        for record in records:
            if not record.cancel_reason:
                cls.raise_user_error('Please fill the Cancel reason')
        pass

    @staticmethod
    def default_employee():
        pool = Pool()
        User = pool.get('res.user')
        user = User(Transaction().user)
        employee = user.employee
        return employee.id if employee else None

    @fields.depends('employee')
    def on_change_employee(self, name=None):
        self.salary_code = self.employee.salary_code
        self.designation = self.employee.designation
        self.department = self.employee.department
        self.pay_in_band = self.employee.pay_in_band
        pool = Pool()
        hrcontract = pool.get('hr.contract')
        contracts = hrcontract.search([('employee', '=', self.employee),
                                       ('active', '<=', True)])
        for contract in contracts:
            self.basic_pay = contract.basic

    @classmethod
    def write(cls, *args):
        actions = iter(args)
        for mechanisms, values in zip(actions, actions):
            if 'installment_no' in values.keys():
                cls.change_loan_installment(mechanisms, values)
        super(ComputerLoan, cls).write(*args)

    @classmethod
    def change_loan_installment(cls, records, values):
        cursor = Transaction().connection.cursor()
        LoanLine = Pool().get('loan.line')
        for loan in records:
            cursor.execute(
                'SELECT sum(amount) FROM loan_line WHERE loan=%s \
                AND status = %s', (loan.id, 'done'))
            total_amount = cursor.fetchone()
            if total_amount[0]:
                reschedule = loan.amount_required - total_amount[0]
                cls.write(records, {
                    'payout': total_amount[0],
                    'reschedule': reschedule
                })
                amount = (reschedule / values['installment_no'])
            else:
                amount = (loan.amount_required / values['installment_no'])
            cursor.execute(
                'delete FROM loan_line WHERE loan=%s \
            AND status != %s', (loan.id, 'done'))
            count = 0
            for line in range(1, int(values['installment_no']) + 1):
                mydate = datetime.datetime.now().month
                month = mydate - 1
                if month + line > 12:
                    count += 1
                    if count > 12:
                        count = 1
                    months = datetime.date(1900, count, 1).strftime('%B')
                else:
                    months = datetime.date(1900, month + line,
                                           1).strftime('%B')
                vals = {
                    'month': months,
                    'amount': amount,
                    'status': 'pending',
                    'loan': loan.id
                }
                line = LoanLine.create([vals])

    @classmethod
    def loan_installment(cls, records):
        count = 0
        LoanLine = Pool().get('loan.line')
        for loan in records:
            amount = (loan.amount_required / loan.installment_no)
            for line in range(1, int(loan.installment_no) + 1):
                mydate = datetime.datetime.now().month
                month = mydate - 1
                if month + line > 12:
                    count += 1
                    if count > 12:
                        count = 1
                    months = datetime.date(1900, count, 1).strftime('%B')
                else:
                    months = datetime.date(1900, month + line,
                                           1).strftime('%B')
                vals = {
                    'month': months,
                    'amount': amount,
                    'status': 'pending',
                    'loan': loan.id
                }
                line = LoanLine.create([vals])
Exemple #24
0
class Account(DeactivableMixin, tree('distribution_parents'), tree(), ModelSQL,
              ModelView):
    'Analytic Account'
    __name__ = 'analytic_account.account'
    name = fields.Char('Name', required=True, translate=True, select=True)
    code = fields.Char('Code', select=True)
    company = fields.Many2One('company.company', 'Company', required=True)
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'),
        'on_change_with_currency')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'on_change_with_currency_digits')
    type = fields.Selection([
        ('root', 'Root'),
        ('view', 'View'),
        ('normal', 'Normal'),
        ('distribution', 'Distribution'),
    ],
                            'Type',
                            required=True)
    root = fields.Many2One('analytic_account.account',
                           'Root',
                           select=True,
                           domain=[
                               ('company', '=', Eval('company', -1)),
                               ('parent', '=', None),
                               ('type', '=', 'root'),
                           ],
                           states={
                               'invisible': Eval('type') == 'root',
                               'required': Eval('type') != 'root',
                           },
                           depends=['company', 'type'])
    parent = fields.Many2One('analytic_account.account',
                             'Parent',
                             select=True,
                             domain=[
                                 'OR',
                                 ('root', '=', Eval('root', -1)),
                                 ('parent', '=', None),
                             ],
                             states={
                                 'invisible': Eval('type') == 'root',
                                 'required': Eval('type') != 'root',
                             },
                             depends=['root', 'type'])
    childs = fields.One2Many('analytic_account.account',
                             'parent',
                             'Children',
                             states={
                                 'invisible': Eval('id', -1) < 0,
                             },
                             domain=[
                                 ('company', '=', Eval('company', -1)),
                             ],
                             depends=['company'])
    balance = fields.Function(
        fields.Numeric('Balance',
                       digits=(16, Eval('currency_digits', 1)),
                       depends=['currency_digits']), 'get_balance')
    credit = fields.Function(
        fields.Numeric('Credit',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_credit_debit')
    debit = fields.Function(
        fields.Numeric('Debit',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_credit_debit')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('opened', 'Opened'),
        ('closed', 'Closed'),
    ],
                             'State',
                             required=True)
    note = fields.Text('Note')
    distributions = fields.One2Many('analytic_account.account.distribution',
                                    'parent',
                                    "Distributions",
                                    states={
                                        'invisible':
                                        Eval('type') != 'distribution',
                                        'required':
                                        Eval('type') == 'distribution',
                                    },
                                    depends=['type'])
    distribution_parents = fields.Many2Many(
        'analytic_account.account.distribution',
        'account',
        'parent',
        "Distribution Parents",
        readonly=True)

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

    @classmethod
    def __register__(cls, module_name):
        super(Account, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)

        # Migration from 4.0: remove currency
        table.not_null_action('currency', action='remove')

        # Migration from 5.0: remove display_balance
        table.drop_column('display_balance')

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

    @staticmethod
    def default_type():
        return 'normal'

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    def validate(cls, accounts):
        super(Account, cls).validate(accounts)
        for account in accounts:
            account.check_distribution()

    def check_distribution(self):
        if self.type != 'distribution':
            return
        if sum((d.ratio for d in self.distributions)) != 1:
            raise AccountValidationError(
                gettext('analytic_account.msg_invalid_distribution',
                        account=self.rec_name))

    @fields.depends('company')
    def on_change_with_currency(self, name=None):
        if self.company:
            return self.company.currency.id

    @fields.depends('company')
    def on_change_with_currency_digits(self, name=None):
        if self.company:
            return self.company.currency.digits
        return 2

    @fields.depends('parent', 'type', '_parent_parent.root',
                    '_parent_parent.type')
    def on_change_parent(self):
        if self.parent and self.type != 'root':
            if self.parent.type == 'root':
                self.root = self.parent
            else:
                self.root = self.parent.root
        else:
            self.root = None

    @classmethod
    def get_balance(cls, accounts, name):
        pool = Pool()
        Line = pool.get('analytic_account.line')
        MoveLine = pool.get('account.move.line')
        cursor = Transaction().connection.cursor()
        table = cls.__table__()
        line = Line.__table__()
        move_line = MoveLine.__table__()

        ids = [a.id for a in accounts]
        childs = cls.search([('parent', 'child_of', ids)])
        all_ids = list({}.fromkeys(ids + [c.id for c in childs]).keys())

        id2account = {}
        all_accounts = cls.browse(all_ids)
        for account in all_accounts:
            id2account[account.id] = account

        line_query = Line.query_get(line)
        cursor.execute(
            *table.join(line, 'LEFT', condition=table.id == line.account).join(
                move_line, 'LEFT', condition=move_line.id == line.move_line).
            select(table.id,
                   Sum(Coalesce(line.credit, 0) - Coalesce(line.debit, 0)),
                   where=(table.type != 'view')
                   & table.id.in_(all_ids)
                   & (table.active == True) & line_query,
                   group_by=table.id))
        account_sum = defaultdict(Decimal)
        for account_id, value in cursor.fetchall():
            account_sum.setdefault(account_id, Decimal('0.0'))
            # SQLite uses float for SUM
            if not isinstance(value, Decimal):
                value = Decimal(str(value))
            account_sum[account_id] += value

        balances = {}
        for account in accounts:
            balance = Decimal()
            childs = cls.search([
                ('parent', 'child_of', [account.id]),
            ])
            for child in childs:
                balance += account_sum[child.id]
            exp = Decimal(str(10.0**-account.currency_digits))
            balances[account.id] = balance.quantize(exp)
        return balances

    @classmethod
    def get_credit_debit(cls, accounts, names):
        pool = Pool()
        Line = pool.get('analytic_account.line')
        MoveLine = pool.get('account.move.line')
        cursor = Transaction().connection.cursor()
        table = cls.__table__()
        line = Line.__table__()
        move_line = MoveLine.__table__()

        result = {}
        ids = [a.id for a in accounts]
        for name in names:
            if name not in ('credit', 'debit'):
                raise Exception('Bad argument')
            result[name] = {}.fromkeys(ids, Decimal('0.0'))

        id2account = {}
        for account in accounts:
            id2account[account.id] = account

        line_query = Line.query_get(line)
        columns = [table.id]
        for name in names:
            columns.append(Sum(Coalesce(Column(line, name), 0)))
        cursor.execute(
            *table.join(line, 'LEFT', condition=table.id == line.account).join(
                move_line, 'LEFT', condition=move_line.id ==
                line.move_line).select(*columns,
                                       where=(table.type != 'view')
                                       & table.id.in_(ids)
                                       & (table.active == True) & line_query,
                                       group_by=table.id))

        for row in cursor.fetchall():
            account_id = row[0]
            for i, name in enumerate(names, 1):
                value = row[i]
                # SQLite uses float for SUM
                if not isinstance(value, Decimal):
                    value = Decimal(str(value))
                result[name][account_id] += value
        for account in accounts:
            for name in names:
                exp = Decimal(str(10.0**-account.currency_digits))
                result[name][account.id] = (
                    result[name][account.id].quantize(exp))
        return result

    def get_rec_name(self, name):
        if self.code:
            return self.code + ' - ' + str(self.name)
        else:
            return str(self.name)

    @classmethod
    def search_rec_name(cls, name, clause):
        accounts = cls.search([('code', ) + tuple(clause[1:])], limit=1)
        if accounts:
            return [('code', ) + tuple(clause[1:])]
        else:
            return [(cls._rec_name, ) + tuple(clause[1:])]

    def distribute(self, amount):
        "Return a list of (account, amount) distribution"
        assert self.type in {'normal', 'distribution'}
        if self.type == 'normal':
            return [(self, amount)]
        else:
            result = []
            remainder = amount
            for distribution in self.distributions:
                account = distribution.account
                ratio = distribution.ratio
                current_amount = self.currency.round(amount * ratio)
                remainder -= current_amount
                result.extend(account.distribute(current_amount))
            if remainder:
                i = 0
                while remainder:
                    account, amount = result[i]
                    rounding = self.currency.rounding.copy_sign(remainder)
                    result[i] = (account, amount - rounding)
                    remainder -= rounding
                    i = (i + 1) % len(result)
            return result
Exemple #25
0
class Complaint(Workflow, ModelSQL, ModelView):
    'Customer Complaint'
    __name__ = 'sale.complaint'
    _rec_name = 'number'

    _states = {
        'readonly': Eval('state') != 'draft',
    }

    number = fields.Char('Number', readonly=True, select=True)
    reference = fields.Char('Reference', select=True)
    date = fields.Date('Date', states=_states)
    customer = fields.Many2One('party.party',
                               "Customer",
                               required=True,
                               states=_states,
                               context={
                                   'company': Eval('company', -1),
                               },
                               depends={'company'})
    address = fields.Many2One('party.address',
                              'Address',
                              domain=[('party', '=', Eval('customer'))],
                              states=_states)
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              states=_states)
    employee = fields.Many2One('company.employee', 'Employee', states=_states)
    type = fields.Many2One('sale.complaint.type',
                           'Type',
                           required=True,
                           states=_states)
    origin = fields.Reference(
        'Origin',
        selection='get_origin',
        domain={
            'sale.sale': [
                If(Eval('customer'), ('party', '=', Eval('customer')), ()),
                ('company', '=', Eval('company')),
                ('state', 'in', ['confirmed', 'processing', 'done']),
            ],
            'sale.line': [
                ('type', '=', 'line'),
                If(Eval('customer'), ('sale.party', '=', Eval('customer')),
                   ()),
                ('sale.company', '=', Eval('company')),
                ('sale.state', 'in', ['confirmed', 'processing', 'done']),
            ],
            'account.invoice': [
                If(Eval('customer'), ('party', '=', Eval('customer')), ()),
                ('company', '=', Eval('company')),
                ('type', '=', 'out'),
                ('state', 'in', ['posted', 'paid']),
            ],
            'account.invoice.line': [
                ('type', '=', 'line'),
                If(Eval('customer'), ('invoice.party', '=', Eval('customer')),
                   ()),
                ('invoice.company', '=', Eval('company')),
                ('invoice.type', '=', 'out'),
                ('invoice.state', 'in', ['posted', 'paid']),
            ],
        },
        states={
            'readonly': ((Eval('state') != 'draft')
                         | Bool(Eval('actions', [0]))),
            'required': Bool(Eval('origin_model')),
        },
        depends={'origin_model'})
    origin_id = fields.Function(fields.Integer('Origin ID'),
                                'on_change_with_origin_id')
    origin_model = fields.Function(fields.Char('Origin Model'),
                                   'on_change_with_origin_model')
    description = fields.Text('Description', states=_states)
    actions = fields.One2Many(
        'sale.complaint.action',
        'complaint',
        'Actions',
        states={
            'readonly':
            ((Eval('state') != 'draft')
             | (If(~Eval('origin_id', 0), 0, Eval('origin_id', 0)) <= 0)),
        },
        depends={'origin_model'})
    state = fields.Selection([
        ('draft', 'Draft'),
        ('waiting', 'Waiting'),
        ('approved', 'Approved'),
        ('rejected', 'Rejected'),
        ('done', 'Done'),
        ('cancelled', 'Cancelled'),
    ],
                             "State",
                             readonly=True,
                             required=True,
                             sort=False)

    @classmethod
    def __setup__(cls):
        super(Complaint, cls).__setup__()
        cls._order.insert(0, ('date', 'DESC'))
        cls._transitions |= set((
            ('draft', 'waiting'),
            ('waiting', 'draft'),
            ('waiting', 'approved'),
            ('waiting', 'rejected'),
            ('approved', 'done'),
            ('approved', 'draft'),
            ('draft', 'cancelled'),
            ('waiting', 'cancelled'),
            ('done', 'draft'),
            ('rejected', 'draft'),
            ('cancelled', 'draft'),
        ))
        cls._buttons.update({
            'cancel': {
                'invisible': ~Eval('state').in_(['draft', 'waiting']),
                'depends': ['state'],
            },
            'draft': {
                'invisible':
                ~Eval('state').in_(['waiting', 'done', 'cancelled']),
                'icon':
                If(
                    Eval('state').in_(['done', 'cancelled']), 'tryton-undo',
                    'tryton-back'),
                'depends': ['state'],
            },
            'wait': {
                'invisible': ~Eval('state').in_(['draft']),
                'depends': ['state'],
            },
            'approve': {
                'invisible': ~Eval('state').in_(['waiting']),
                'depends': ['state'],
            },
            'reject': {
                'invisible': ~Eval('state').in_(['waiting']),
                'depends': ['state'],
            },
            'process': {
                'invisible': ~Eval('state').in_(['approved']),
                'depends': ['state'],
            },
        })

        actions_domains = cls._actions_domains()
        actions_domain = [('action', 'in', actions_domains.pop(None))]
        for model, actions in actions_domains.items():
            actions_domain = If(
                Eval('origin_model') == model, [('action', 'in', actions)],
                actions_domain)
        cls.actions.domain = [actions_domain]

    @classmethod
    def __register__(cls, module_name):
        table_h = cls.__table_handler__(module_name)

        # Migration from 3.8: rename reference into number
        if (table_h.column_exist('reference')
                and not table_h.column_exist('number')):
            table_h.column_rename('reference', 'number')

        super(Complaint, cls).__register__(module_name)

    @classmethod
    def _actions_domains(cls):
        return {
            None: [],
            'sale.sale': ['sale_return'],
            'sale.line': ['sale_return'],
            'account.invoice': ['credit_note'],
            'account.invoice.line': ['credit_note'],
        }

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

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

    @staticmethod
    def default_state():
        return 'draft'

    @fields.depends('type')
    def get_origin(self):
        if self.type:
            origin = self.type.origin
            return [('', ''), (origin.model, origin.name)]
        else:
            return []

    @fields.depends('origin', 'customer')
    def on_change_origin(self):
        pool = Pool()
        Sale = pool.get('sale.sale')
        SaleLine = pool.get('sale.line')
        Invoice = pool.get('account.invoice')
        InvoiceLine = pool.get('account.invoice.line')
        if not self.customer and self.origin and self.origin.id >= 0:
            if isinstance(self.origin, Sale):
                self.customer = self.origin.party
            elif isinstance(self.origin, SaleLine):
                self.customer = self.origin.sale.party
            elif isinstance(self.origin, Invoice):
                self.customer = self.origin.party
            elif isinstance(self.origin, InvoiceLine) and self.origin.invoice:
                self.customer = self.origin.invoice.party

    @fields.depends('origin')
    def on_change_with_origin_id(self, name=None):
        if self.origin:
            return self.origin.id

    @fields.depends('origin')
    def on_change_with_origin_model(self, name=None):
        if self.origin:
            return self.origin.__class__.__name__

    @classmethod
    def view_attributes(cls):
        return super().view_attributes() + [
            ('/tree', 'visual', If(Eval('state') == 'cancelled', 'muted', '')),
        ]

    @classmethod
    def create(cls, vlist):
        pool = Pool()
        Configuration = pool.get('sale.configuration')

        config = Configuration(1)
        vlist = [v.copy() for v in vlist]
        default_company = cls.default_company()
        for values in vlist:
            if values.get('number') is None:
                values['number'] = config.get_multivalue(
                    'complaint_sequence',
                    company=values.get('company', default_company)).get()
        return super(Complaint, cls).create(vlist)

    @classmethod
    def copy(cls, complaints, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('number', None)
        return super(Complaint, cls).copy(complaints, default=default)

    @classmethod
    def delete(cls, complaints):
        for complaint in complaints:
            if complaint.state != 'draft':
                raise AccessError(
                    gettext('sale_complaint.msg_complaint_delete_draft',
                            complaint=complaint.rec_name))
        super(Complaint, cls).delete(complaints)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, complaints):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, complaints):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('waiting')
    def wait(cls, complaints):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('approved')
    def approve(cls, complaints):
        pool = Pool()
        Configuration = pool.get('sale.configuration')
        transaction = Transaction()
        context = transaction.context
        config = Configuration(1)
        with transaction.set_context(
                queue_scheduled_at=config.sale_process_after,
                queue_batch=context.get('queue_batch', True)):
            cls.__queue__.process(complaints)

    @classmethod
    @ModelView.button
    @Workflow.transition('rejected')
    def reject(cls, complaints):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def process(cls, complaints):
        pool = Pool()
        Action = pool.get('sale.complaint.action')
        results = defaultdict(list)
        actions = defaultdict(list)
        for complaint in complaints:
            for action in complaint.actions:
                if action.result:
                    continue
                result = action.do()
                results[result.__class__].append(result)
                actions[result.__class__].append(action)
        for kls, records in results.items():
            kls.save(records)
            for action, record in zip(actions[kls], records):
                action.result = record
        Action.save(sum(list(actions.values()), []))
Exemple #26
0
class AnalyticMixin(object):

    analytic_accounts = fields.One2Many('analytic.account.entry',
                                        'origin',
                                        'Analytic Accounts',
                                        size=Eval('analytic_accounts_size', 0),
                                        depends=['analytic_accounts_size'])
    analytic_accounts_size = fields.Function(
        fields.Integer('Analytic Accounts Size'), 'get_analytic_accounts_size')

    @classmethod
    def __register__(cls, module_name):
        pool = Pool()
        AccountEntry = pool.get('analytic.account.entry')
        cursor = Transaction().connection.cursor()

        super(AnalyticMixin, cls).__register__(module_name)

        handler = cls.__table_handler__(module_name)
        # Migration from 3.4: analytic accounting changed to reference field
        if handler.column_exist('analytic_accounts'):
            entry = AccountEntry.__table__()
            table = cls.__table__()
            cursor.execute(
                *table.select(table.id,
                              table.analytic_accounts,
                              where=table.analytic_accounts != None))
            for line_id, selection_id in cursor.fetchall():
                cursor.execute(
                    *entry.update(columns=[entry.origin],
                                  values=['%s,%s' % (cls.__name__, line_id)],
                                  where=entry.selection == selection_id))
            handler.drop_column('analytic_accounts')

    @classmethod
    def analytic_accounts_domain(cls):
        context = Transaction().context.copy()
        context['context'] = context
        return PYSONDecoder(context).decode(PYSONEncoder().encode(
            cls.analytic_accounts.domain))

    @classmethod
    def default_analytic_accounts(cls):
        pool = Pool()
        AnalyticAccount = pool.get('analytic_account.account')

        accounts = []
        root_accounts = AnalyticAccount.search(cls.analytic_accounts_domain() +
                                               [
                                                   ('parent', '=', None),
                                               ])
        for account in root_accounts:
            accounts.append({
                'root': account.id,
            })
        return accounts

    @classmethod
    def default_analytic_accounts_size(cls):
        pool = Pool()
        AnalyticAccount = pool.get('analytic_account.account')
        return len(
            AnalyticAccount.search(cls.analytic_accounts_domain() + [
                ('type', '=', 'root'),
            ]))

    @classmethod
    def get_analytic_accounts_size(cls, records, name):
        roots = cls.default_analytic_accounts_size()
        return {r.id: roots for r in records}
class ModelInfo(ModelView):
    'Model Name'

    __name__ = 'ir.model.debug.model_info'

    model_name = fields.Selection('get_possible_model_names', 'Model Name')
    field_infos = fields.One2Many('ir.model.debug.model_info.field_info', '',
                                  'Fields Infos')
    hide_functions = fields.Boolean('Hide Functions')
    filter_value = fields.Selection([('name', 'Name'), ('kind', 'Kind'),
                                     ('string', 'String')], 'Filter Value')
    id_to_calculate = fields.Integer('Id To Calculate')
    to_evaluate = fields.Char(
        'To Evaluate',
        states={'invisible': ~Bool(Eval('id_to_calculate', False))},
        help="Use the 'instance' keyword to get the instanciated model",
        depends=['id_to_calculate'])
    evaluation_result = fields.Text(
        'Evaluation Result',
        states={'invisible': ~Bool(Eval('id_to_calculate', False))},
        readonly=True,
        depends=['id_to_calculate'])
    must_raise_exception = fields.Boolean('Must Raise Exception')
    previous_runs = fields.Text(
        'Previous runs',
        states={'invisible': ~Bool(Eval('id_to_calculate', False))},
        readonly=True,
        depends=['id_to_calculate'])

    @classmethod
    def __setup__(cls):
        super(ModelInfo, cls).__setup__()
        cls.__rpc__.update({
            'raw_model_infos': RPC(),
            'raw_module_infos': RPC(),
            'raw_field_infos': RPC(),
        })

    @classmethod
    def get_possible_model_names(cls):
        pool = Pool()
        return list([
            (x, x) for x in pool._pool[pool.database_name]['model'].iterkeys()
        ])

    def get_field_info(self, field, field_name):

        info = Pool().get('ir.model.debug.model_info.field_info')()
        info.name = field_name
        info.string = field.string
        if isinstance(field, fields.Function):
            if self.hide_functions:
                return None
            info.is_function = True
            real_field = field._field
        else:
            info.is_function = False
            real_field = field
        info.kind = real_field.__class__.__name__
        if isinstance(field, (fields.Many2One, fields.One2Many)):
            info.target_model = field.model_name
        elif isinstance(field, fields.Many2Many):
            if field.target:
                info.target_model = Pool().get(
                    field.relation_name)._fields[field.target].model_name
            else:
                info.target_model = field.relation_name
        else:
            info.target_model = ''
        for elem in ('required', 'readonly', 'invisible'):
            setattr(info, 'is_%s' % elem, getattr(field, elem, False))
            setattr(info, 'state_%s' % elem, repr(field.states.get(elem, {})))
        field_domain = getattr(field, 'domain', None)
        if field_domain:
            info.has_domain = True
            info.field_domain = repr(field_domain)
        return info

    @classmethod
    def default_filter_value(cls):
        return 'name'

    @fields.depends('model_name', 'hide_functions', 'filter_value',
                    'field_infos', 'id_to_calculate')
    def on_change_filter_value(self):
        self.recalculate_field_infos()

    @fields.depends('model_name', 'hide_functions', 'filter_value',
                    'field_infos', 'id_to_calculate')
    def on_change_hide_functions(self):
        self.recalculate_field_infos()

    @fields.depends('model_name', 'hide_functions', 'filter_value',
                    'field_infos', 'id_to_calculate')
    def on_change_model_name(self):
        self.to_evaluate = ''
        self.evaluation_result = ''
        self.recalculate_field_infos()

    @fields.depends('model_name', 'id_to_calculate', 'to_evaluate',
                    'must_raise_exception', 'previous_runs')
    def on_change_to_evaluate(self):
        if not self.to_evaluate:
            self.evaluation_result = ''
            return
        if not self.id_to_calculate or not self.model_name:
            self.evaluation_result = ''
            self.previous_runs = ''
            return
        if self.previous_runs:
            previous_runs = self.previous_runs[1:].split('\n\n' + '#' * 20 +
                                                         '\n\n')
        else:
            previous_runs = ['']
        if previous_runs[0] != self.to_evaluate:
            self.previous_runs = '\n' + ('\n\n' + '#' * 20 + '\n\n').join(
                [self.to_evaluate] + [x for x in previous_runs if x])
        try:
            context = {
                'instance': Pool().get(self.model_name)(self.id_to_calculate),
            }
            self.evaluation_result = pprint.pformat(
                eval(self.to_evaluate, context))
        except Exception, exc:
            if self.must_raise_exception:
                raise
            self.evaluation_result = 'ERROR: %s' % str(exc)
Exemple #28
0
class NereidStaticFile(ModelSQL, ModelView):
    "Static files for Nereid"
    __name__ = "nereid.static.file"

    name = fields.Char('File Name', select=True, required=True)
    folder = fields.Many2One('nereid.static.folder',
                             'Folder',
                             select=True,
                             required=True)

    #: This function field returns the field contents. This is useful if the
    #: field is going to be displayed on the clients.
    file_binary = fields.Function(
        fields.Binary('File', filename='name'),
        'get_file_binary',
        'set_file_binary',
    )

    #: Full path to the file in the filesystem
    file_path = fields.Function(fields.Char('File Path'), 'get_file_path')

    #: URL that can be used to idenfity the resource. Note that the value
    #: of this field is available only when called within a request context.
    #: In other words the URL is valid only when called in a nereid request.
    url = fields.Function(fields.Char('URL'), 'get_url')

    # Sequence
    sequence = fields.Integer('Sequence', select=True)

    # File mimetype
    mimetype = fields.Function(fields.Char('Mimetype'), getter='get_mimetype')

    @classmethod
    def __setup__(cls):
        super(NereidStaticFile, cls).__setup__()
        cls._sql_constraints += [
            ('name_folder_uniq', 'UNIQUE(name, folder)',
             'The Name of the Static File must be unique in a folder.!'),
        ]
        cls._error_messages.update({
            'invalid_file_name':
            """Invalid file name:
                (1) '..' in file name (OR)
                (2) file name contains '/'""",
        })

    @staticmethod
    def default_sequence():
        return 10

    def get_mimetype(self, name):
        """
        This method detects and returns the mimetype for the static file.

        The python mimetypes module returns a tuple of the form -:

        >>> mimetypes.guess_type(file_name)
        (file_mimetype, encoding)

        which can then be used to fill the `mimetype` field. Some example types
        are -:
            * image/png
            * application/pdf
        etc.
        """
        return mimetypes.guess_type(self.name)[0]

    def get_url(self, name):
        """Return the url if within an active request context or return
        False values
        """
        if _request_ctx_stack.top is None:
            return None

        return url_for('nereid.static.file.send_static_file',
                       folder=self.folder.name,
                       name=self.name)

    @staticmethod
    def get_nereid_base_path():
        """
        Returns base path for nereid, where all the static files would be
        stored.

        By Default it is:

        <Tryton Data Path>/<Database Name>/nereid
        """
        cursor = Transaction().cursor
        return os.path.join(config.get('database', 'path'),
                            cursor.database_name, "nereid")

    def _set_file_binary(self, value):
        """
        Setter for static file that stores file in file system

        :param value: The value to set
        """
        file_binary = fields.Binary.cast(bytes(value))
        # If the folder does not exist, create it recursively
        directory = os.path.dirname(self.file_path)
        if not os.path.isdir(directory):
            os.makedirs(directory)
        with open(self.file_path, 'wb') as file_writer:
            file_writer.write(file_binary)

    @classmethod
    def set_file_binary(cls, files, name, value):
        """
        Setter for the functional binary field.

        :param files: Records
        :param name: Ignored
        :param value: The file buffer
        """
        for static_file in files:
            static_file._set_file_binary(value)

    def get_file_binary(self, name):
        '''
        Getter for the binary_file field. This fetches the file from the
        file system, coverts it to buffer and returns it.

        :param name: Field name
        :return: Bytes
        '''
        location = self.file_path
        with open(location, 'rb') as file_reader:
            return fields.Binary.cast(file_reader.read())

    def get_file_path(self, name):
        """
        Returns the full path to the file in the file system

        :param name: Field name
        :return: File path
        """
        return os.path.abspath(
            os.path.join(self.get_nereid_base_path(), self.folder.name,
                         self.name))

    @classmethod
    def validate(cls, files):
        """
        Validates the records.

        :param files: active record list of static files
        """
        super(NereidStaticFile, cls).validate(files)
        for file in files:
            file.check_file_name()

    def check_file_name(self):
        '''
        Check the validity of folder name
        Allowing the use of / or . will be risky as that could
        eventually lead to previlege escalation
        '''
        if ('..' in self.name) or ('/' in self.name):
            self.raise_user_error("invalid_file_name")

    @classmethod
    @route("/static-file/<folder>/<name>", methods=["GET"])
    def send_static_file(cls, folder, name):
        """
        Invokes the send_file method in nereid.helpers to send a file as the
        response to the request. The file is sent in a way which is as
        efficient as possible. For example nereid will use the X-Send_file
        header to make nginx send the file if possible.

        :param folder: name of the folder
        :param name: name of the file
        """
        # TODO: Separate this search and find into separate cached method

        files = cls.search([('folder.name', '=', folder), ('name', '=', name)])
        if not files:
            abort(404)
        return send_file(files[0].file_path)
Exemple #29
0
class Sample(metaclass=PoolMeta):
    __name__ = 'lims.sample'

    plant = fields.Function(fields.Many2One('lims.plant', 'Plant'),
        'get_plant', searcher='search_plant')
    equipment = fields.Many2One('lims.equipment', 'Equipment',
        domain=['OR', ('id', '=', Eval('equipment')),
            ('party', '=', Eval('party'))],
        depends=['party'], select=True)
    equipment_template = fields.Function(fields.Many2One(
        'lims.equipment.template', 'Equipment Template'),
        'get_equipment_field')
    equipment_model = fields.Function(fields.Char('Equipment Model'),
        'get_equipment_field')
    equipment_serial_number = fields.Function(fields.Char(
        'Equipment Serial Number'), 'get_equipment_field')
    equipment_name = fields.Function(fields.Char(
        'Equipment Name'), 'get_equipment_field')
    component = fields.Many2One('lims.component', 'Component',
        domain=['OR', ('id', '=', Eval('component')),
            ('equipment', '=', Eval('equipment'))],
        depends=['equipment'])
    comercial_product = fields.Many2One('lims.comercial.product',
        'Comercial Product')
    ind_sampling_date = fields.Date('Sampling date')
    ind_volume = fields.Float('Received volume')
    sampling_type = fields.Many2One('lims.sampling.type',
        'Sampling Type')
    ind_operational_detail = fields.Text('Operational detail')
    ind_work_environment = fields.Text('Work environment')
    ind_analysis_reason = fields.Text('Reason for analysis')
    missing_data = fields.Boolean('Missing data')
    attributes_domain = fields.Function(fields.Many2Many(
        'lims.sample.attribute', None, None, 'Attributes domain'),
        'on_change_with_attributes_domain')
    sample_photo = fields.Binary('Sample Photo',
        file_id='sample_photo_id', store_prefix='sample')
    sample_photo_id = fields.Char('Sample Photo ID', readonly=True)
    label_photo = fields.Binary('Label Photo',
        file_id='label_photo_id', store_prefix='sample')
    label_photo_id = fields.Char('Label Photo ID', readonly=True)
    oil_added = fields.Float('Liters Oil added')
    ind_equipment = fields.Integer('Equipment')
    ind_equipment_uom = fields.Selection([
        ('hs', 'Hs.'),
        ('km', 'Km.'),
        ], 'UoM', sort=False)
    ind_component = fields.Integer('Component')
    ind_component_uom = fields.Selection([
        ('hs', 'Hs.'),
        ('km', 'Km.'),
        ], 'UoM', sort=False)
    ind_oil = fields.Integer('Oil')
    ind_oil_uom = fields.Selection([
        ('hs', 'Hs.'),
        ('km', 'Km.'),
        ], 'UoM', sort=False)
    oil_changed = fields.Selection([
        (None, '-'),
        ('yes', 'Yes'),
        ('no', 'No'),
        ], 'Did change Oil?', sort=False)
    oil_changed_string = oil_changed.translated('oil_changed')
    oil_filter_changed = fields.Selection([
        (None, '-'),
        ('yes', 'Yes'),
        ('no', 'No'),
        ], 'Did change Oil Filter?', sort=False)
    oil_filter_changed_string = oil_filter_changed.translated(
        'oil_filter_changed')
    air_filter_changed = fields.Selection([
        (None, '-'),
        ('yes', 'Yes'),
        ('no', 'No'),
        ], 'Did change Air Filter?', sort=False)
    air_filter_changed_string = air_filter_changed.translated(
        'air_filter_changed')
    edition_log = fields.One2Many('lims.sample.edition.log', 'sample',
        'Edition log', readonly=True)

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        table_h = cls.__table_handler__(module_name)
        sample = cls.__table__()

        super().__register__(module_name)

        if table_h.column_exist('changed_oil'):
            cursor.execute(*sample.update([sample.oil_changed],
                [Case((sample.changed_oil == Literal(True),
                    'yes'), else_='no')]))
            table_h.drop_column('changed_oil')
        if table_h.column_exist('changed_oil_filter'):
            cursor.execute(*sample.update([sample.oil_filter_changed],
                [Case((sample.changed_oil_filter == Literal(True),
                    'yes'), else_='no')]))
            table_h.drop_column('changed_oil_filter')
        if table_h.column_exist('changed_air_filter'):
            cursor.execute(*sample.update([sample.air_filter_changed],
                [Case((sample.changed_air_filter == Literal(True),
                    'yes'), else_='no')]))
            table_h.drop_column('changed_air_filter')
        if table_h.column_exist('hours_equipment'):
            cursor.execute(*sample.update([sample.ind_equipment],
                [sample.hours_equipment]))
            table_h.drop_column('hours_equipment')
        if table_h.column_exist('hours_component'):
            cursor.execute(*sample.update([sample.ind_component],
                [sample.hours_component]))
            table_h.drop_column('hours_component')
        if table_h.column_exist('hours_oil'):
            cursor.execute(*sample.update([sample.ind_oil],
                [sample.hours_oil]))
            table_h.drop_column('hours_oil')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.product_type.states['readonly'] = Bool(Eval('component'))
        if 'component' not in cls.product_type.depends:
            cls.product_type.depends.append('component')
        cls.matrix.states['readonly'] = Bool(Eval('comercial_product'))
        if 'comercial_product' not in cls.matrix.depends:
            cls.matrix.depends.append('comercial_product')
        cls.attributes.domain = [('id', 'in', Eval('attributes_domain'))]
        if 'attributes_domain' not in cls.attributes.depends:
            cls.attributes.depends.append('attributes_domain')

    @staticmethod
    def default_ind_equipment_uom():
        return 'hs'

    @staticmethod
    def default_ind_component_uom():
        return 'hs'

    @staticmethod
    def default_ind_oil_uom():
        return 'hs'

    @fields.depends('equipment', 'component')
    def on_change_equipment(self):
        if not self.equipment and self.component:
            self.component = None

    @fields.depends('component')
    def on_change_component(self):
        if self.component:
            if self.component.product_type:
                self.product_type = self.component.product_type.id
            if self.component.comercial_product:
                self.comercial_product = self.component.comercial_product.id
                self.on_change_comercial_product()

    @fields.depends('comercial_product')
    def on_change_comercial_product(self):
        if self.comercial_product and self.comercial_product.matrix:
            self.matrix = self.comercial_product.matrix.id

    @fields.depends('product_type', '_parent_product_type.attribute_set')
    def on_change_with_attributes_domain(self, name=None):
        pool = Pool()
        SampleAttributeAttributeSet = pool.get(
            'lims.sample.attribute-attribute.set')
        attribute_set = None
        if self.product_type and self.product_type.attribute_set:
            attribute_set = self.product_type.attribute_set.id
        res = SampleAttributeAttributeSet.search([
            ('attribute_set', '=', attribute_set),
            ])
        return [x.attribute.id for x in res]

    @classmethod
    def get_plant(cls, samples, name):
        result = {}
        for s in samples:
            result[s.id] = s.equipment and s.equipment.plant.id or None
        return result

    @classmethod
    def search_plant(cls, name, clause):
        return [('equipment.plant',) + tuple(clause[1:])]

    def _order_equipment_field(name):
        def order_field(tables):
            Equipment = Pool().get('lims.equipment')
            field = Equipment._fields[name]
            table, _ = tables[None]
            equipment_tables = tables.get('equipment')
            if equipment_tables is None:
                equipment = Equipment.__table__()
                equipment_tables = {
                    None: (equipment, equipment.id == table.equipment),
                    }
                tables['equipment'] = equipment_tables
            return field.convert_order(name, equipment_tables, Equipment)
        return staticmethod(order_field)
    order_plant = _order_equipment_field('plant')

    @classmethod
    def get_equipment_field(cls, samples, names):
        result = {}
        for name in names:
            result[name] = {}
            if cls._fields[name]._type == 'many2one':
                for s in samples:
                    field = getattr(s.equipment, name.replace(
                        'equipment_', ''), None)
                    result[name][s.id] = field.id if field else None
            else:
                for s in samples:
                    result[name][s.id] = getattr(s.equipment, name.replace(
                        'equipment_', ''), None)
        return result

    @classmethod
    def order_component(cls, tables):
        Component = Pool().get('lims.component')
        kind_field = Component._fields['kind']
        location_field = Component._fields['location']
        sample, _ = tables[None]
        component_tables = tables.get('component')
        if component_tables is None:
            component = Component.__table__()
            component_tables = {
                None: (component, component.id == sample.component),
                }
            tables['component'] = component_tables
        order = (
            kind_field.convert_order('kind',
            component_tables, Component) +
            location_field.convert_order('location',
            component_tables, Component))
        return order

    @classmethod
    def _confirm_samples(cls, samples):
        TaskTemplate = Pool().get('lims.administrative.task.template')
        for sample in samples:
            if not sample.component or not sample.comercial_product:
                continue
            if sample.component.comercial_product != sample.comercial_product:
                sample.component.comercial_product = sample.comercial_product
                sample.component.save()
        TaskTemplate.create_tasks('sample_missing_data',
            cls._for_task_missing_data(samples))
        TaskTemplate.create_tasks('sample_insufficient_volume',
            cls._for_task_required_volume(samples))

    @classmethod
    def _for_task_missing_data(cls, samples):
        AdministrativeTask = Pool().get('lims.administrative.task')
        res = []
        for sample in samples:
            if not sample.missing_data:
                continue
            if AdministrativeTask.search([
                    ('type', '=', 'sample_missing_data'),
                    ('origin', '=', '%s,%s' % (cls.__name__, sample.id)),
                    ('state', 'not in', ('done', 'discarded')),
                    ]):
                continue
            res.append(sample)
        return res

    @classmethod
    def _for_task_required_volume(cls, samples):
        pool = Pool()
        EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
        AdministrativeTask = pool.get('lims.administrative.task')
        res = []
        for sample in samples:
            received_volume = sample.ind_volume or 0
            analysis_detail = EntryDetailAnalysis.search([
                ('sample', '=', sample.id)])
            for detail in analysis_detail:
                received_volume -= (detail.analysis.ind_volume or 0)
            if received_volume >= 0:
                continue
            if AdministrativeTask.search([
                    ('type', '=', 'sample_insufficient_volume'),
                    ('origin', '=', '%s,%s' % (cls.__name__, sample.id)),
                    ('state', 'not in', ('done', 'discarded')),
                    ]):
                continue
            res.append(sample)
        return res

    @classmethod
    def delete(cls, samples):
        AdministrativeTask = Pool().get('lims.administrative.task')
        for sample in samples:
            tasks = AdministrativeTask.search([
                ('origin', '=', '%s,%s' % (cls.__name__, sample.id)),
                ('state', 'not in', ('done', 'discarded')),
                ])
            if tasks:
                AdministrativeTask.write(tasks, {'state': 'draft'})
                AdministrativeTask.delete(tasks)
        super().delete(samples)
Exemple #30
0
class PaymentTermLine(sequence_ordered(), ModelSQL, ModelView):
    'Payment Term Line'
    __name__ = 'account.invoice.payment_term.line'
    payment = fields.Many2One('account.invoice.payment_term',
                              'Payment Term',
                              required=True,
                              ondelete="CASCADE")
    type = fields.Selection([
        ('fixed', 'Fixed'),
        ('percent', 'Percentage on Remainder'),
        ('percent_on_total', 'Percentage on Total'),
        ('remainder', 'Remainder'),
    ],
                            'Type',
                            required=True)
    ratio = fields.Numeric(
        'Ratio',
        digits=(14, 10),
        states={
            'invisible': ~Eval('type').in_(['percent', 'percent_on_total']),
            'required': Eval('type').in_(['percent', 'percent_on_total']),
        },
        depends=['type'])
    divisor = fields.Numeric(
        'Divisor',
        digits=(10, 14),
        states={
            'invisible': ~Eval('type').in_(['percent', 'percent_on_total']),
            'required': Eval('type').in_(['percent', 'percent_on_total']),
        },
        depends=['type'])
    amount = fields.Numeric('Amount',
                            digits=(16, Eval('currency_digits', 2)),
                            states={
                                'invisible': Eval('type') != 'fixed',
                                'required': Eval('type') == 'fixed',
                            },
                            depends=['type', 'currency_digits'])
    currency = fields.Many2One('currency.currency',
                               'Currency',
                               states={
                                   'invisible': Eval('type') != 'fixed',
                                   'required': Eval('type') == 'fixed',
                               },
                               depends=['type'])
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'on_change_with_currency_digits')
    relativedeltas = fields.One2Many('account.invoice.payment_term.line.delta',
                                     'line', 'Deltas')

    @classmethod
    def __register__(cls, module_name):
        sql_table = cls.__table__()
        super(PaymentTermLine, cls).__register__(module_name)
        cursor = Transaction().connection.cursor()
        table = cls.__table_handler__(module_name)

        # Migration from 3.8: rename percentage into ratio
        if table.column_exist('percentage'):
            cursor.execute(*sql_table.update(
                columns=[sql_table.ratio], values=[sql_table.percentage /
                                                   100]))
            table.drop_column('percentage')

    @staticmethod
    def default_currency_digits():
        return 2

    @staticmethod
    def default_type():
        return 'remainder'

    @classmethod
    def default_relativedeltas(cls):
        if Transaction().user == 0:
            return []
        return [{}]

    @fields.depends('type')
    def on_change_type(self):
        if self.type != 'fixed':
            self.amount = Decimal('0.0')
            self.currency = None
        if self.type not in ('percent', 'percent_on_total'):
            self.ratio = Decimal('0.0')
            self.divisor = Decimal('0.0')

    @fields.depends('ratio')
    def on_change_ratio(self):
        if not self.ratio:
            self.divisor = Decimal('0.0')
        else:
            self.divisor = self.round(1 / self.ratio,
                                      self.__class__.divisor.digits[1])

    @fields.depends('divisor')
    def on_change_divisor(self):
        if not self.divisor:
            self.ratio = Decimal('0.0')
        else:
            self.ratio = self.round(1 / self.divisor,
                                    self.__class__.ratio.digits[1])

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

    def get_date(self, date):
        for relativedelta_ in self.relativedeltas:
            date += relativedelta_.get()
        return date

    def get_value(self, remainder, amount, currency):
        Currency = Pool().get('currency.currency')
        if self.type == 'fixed':
            fixed = Currency.compute(self.currency, self.amount, currency)
            return fixed.copy_sign(amount)
        elif self.type == 'percent':
            return currency.round(remainder * self.ratio)
        elif self.type == 'percent_on_total':
            return currency.round(amount * self.ratio)
        elif self.type == 'remainder':
            return currency.round(remainder)
        return None

    @staticmethod
    def round(number, digits):
        quantize = Decimal(10)**-Decimal(digits)
        return Decimal(number).quantize(quantize)

    @classmethod
    def validate(cls, lines):
        super(PaymentTermLine, cls).validate(lines)
        cls.check_ratio_and_divisor(lines)

    @classmethod
    def check_ratio_and_divisor(cls, lines):
        "Check consistency between ratio and divisor"
        # Use a copy because on_change will change the records
        for line in cls.browse(lines):
            if line.type not in ('percent', 'percent_on_total'):
                continue
            if line.ratio is None or line.divisor is None:
                raise PaymentTermValidationError(
                    gettext(
                        'account_invoice'
                        '.msg_payment_term_invalid_ratio_divisor',
                        line=line.rec_name))
            if (line.ratio != round(1 / line.divisor, cls.ratio.digits[1])
                    and line.divisor != round(1 / line.ratio,
                                              cls.divisor.digits[1])):
                raise PaymentTermValidationError(
                    gettext(
                        'account_invoice'
                        '.msg_payment_term_invalid_ratio_divisor',
                        line=line.rec_name))