Пример #1
0
class UIMenu(
        DeactivableMixin,
        sequence_ordered(order='ASC NULLS LAST'),
        tree(separator=' / '),
        ModelSQL, ModelView):
    "UI menu"
    __name__ = 'ir.ui.menu'

    name = fields.Char('Menu', required=True, translate=True)
    childs = fields.One2Many('ir.ui.menu', 'parent', 'Children')
    parent = fields.Many2One('ir.ui.menu', 'Parent Menu', select=True,
            ondelete='CASCADE')
    groups = fields.Many2Many('ir.ui.menu-res.group',
       'menu', 'group', 'Groups')
    complete_name = fields.Function(fields.Char('Complete Name'),
        'get_rec_name', searcher='search_rec_name')
    icon = fields.Selection('list_icons', 'Icon', translate=False)
    action = fields.Function(fields.Reference('Action',
            selection=[
                ('', ''),
                ('ir.action.report', 'ir.action.report'),
                ('ir.action.act_window', 'ir.action.act_window'),
                ('ir.action.wizard', 'ir.action.wizard'),
                ('ir.action.url', 'ir.action.url'),
                ], translate=False), 'get_action', setter='set_action')
    action_keywords = fields.One2Many('ir.action.keyword', 'model',
        'Action Keywords')
    favorite = fields.Function(fields.Boolean('Favorite'), 'get_favorite')

    @classmethod
    def order_complete_name(cls, tables):
        return cls.name.convert_order('name', tables, cls)

    @staticmethod
    def default_icon():
        return 'tryton-folder'

    @classmethod
    def default_sequence(cls):
        return 50

    @staticmethod
    def list_icons():
        pool = Pool()
        Icon = pool.get('ir.ui.icon')
        return sorted(CLIENT_ICONS
            + [(name, name) for _, name in Icon.list_icons()])

    @classmethod
    def search_global(cls, text):
        # TODO improve search clause
        for record in cls.search([
                    ('rec_name', 'ilike', '%%%s%%' % text),
                    ]):
            if record.action:
                yield record, record.rec_name, record.icon

    @classmethod
    def search(cls, domain, offset=0, limit=None, order=None, count=False,
            query=False):
        menus = super(UIMenu, cls).search(domain, offset=offset, limit=limit,
                order=order, count=False, query=query)
        if query:
            return menus

        if menus:
            parent_ids = {x.parent.id for x in menus if x.parent}
            parents = set()
            for sub_parent_ids in grouped_slice(parent_ids):
                parents.update(cls.search([
                            ('id', 'in', list(sub_parent_ids)),
                            ]))
            # Re-browse to avoid side-cache access
            menus = cls.browse([x.id for x in menus
                    if (x.parent and x.parent in parents) or not x.parent])

        if count:
            return len(menus)
        return menus

    @classmethod
    def get_action(cls, menus, name):
        pool = Pool()
        actions = dict((m.id, None) for m in menus)
        with Transaction().set_context(active_test=False):
            menus = cls.browse(menus)
        action_keywords = sum((list(m.action_keywords) for m in menus), [])

        def action_type(keyword):
            return keyword.action.type
        action_keywords.sort(key=action_type)
        for type, action_keywords in groupby(action_keywords, key=action_type):
            action_keywords = list(action_keywords)
            for action_keyword in action_keywords:
                model = action_keyword.model
                actions[model.id] = '%s,-1' % type

            Action = pool.get(type)
            action2keyword = {k.action.id: k for k in action_keywords}
            with Transaction().set_context(active_test=False):
                factions = Action.search([
                        ('action', 'in', list(action2keyword.keys())),
                        ])
            for action in factions:
                model = action2keyword[action.id].model
                actions[model.id] = str(action)
        return actions

    @classmethod
    def set_action(cls, menus, name, value):
        pool = Pool()
        ActionKeyword = pool.get('ir.action.keyword')
        action_keywords = []
        transaction = Transaction()
        for i in range(0, len(menus), transaction.database.IN_MAX):
            sub_menus = menus[i:i + transaction.database.IN_MAX]
            action_keywords += ActionKeyword.search([
                ('keyword', '=', 'tree_open'),
                ('model', 'in', [str(menu) for menu in sub_menus]),
                ])
        if action_keywords:
            with Transaction().set_context(_timestamp=False):
                ActionKeyword.delete(action_keywords)
        if not value:
            return
        if isinstance(value, str):
            action_type, action_id = value.split(',')
        else:
            action_type, action_id = value
        if int(action_id) <= 0:
            return
        Action = pool.get(action_type)
        action = Action(int(action_id))
        to_create = []
        for menu in menus:
            with Transaction().set_context(_timestamp=False):
                to_create.append({
                        'keyword': 'tree_open',
                        'model': str(menu),
                        'action': action.action.id,
                        })
        if to_create:
            ActionKeyword.create(to_create)

    @classmethod
    def get_favorite(cls, menus, name):
        pool = Pool()
        Favorite = pool.get('ir.ui.menu.favorite')
        user = Transaction().user
        favorites = Favorite.search([
                ('menu', 'in', [m.id for m in menus]),
                ('user', '=', user),
                ])
        menu2favorite = dict((m.id, False if m.action else None)
            for m in menus)
        menu2favorite.update(dict((f.menu.id, True) for f in favorites))
        return menu2favorite
Пример #2
0
class Subscription(Workflow, ModelSQL, ModelView):
    "Subscription"
    __name__ = 'sale.subscription'
    _rec_name = 'number'

    company = fields.Many2One(
        'company.company', "Company", required=True, select=True,
        states={
            'readonly': Eval('state') != 'draft',
            },
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=', '!='),
                Eval('context', {}).get('company', -1)),
            ],
        depends=['state'],
        help="Make the subscription belong to the company.")

    number = fields.Char(
        "Number", readonly=True, select=True,
        help="The main identification of the subscription.")
    # TODO revision
    reference = fields.Char(
        "Reference", select=True,
        help="The identification of an external origin.")
    description = fields.Char("Description",
        states={
            'readonly': Eval('state') != 'draft',
            },
        depends=['state'])

    party = fields.Many2One(
        'party.party', "Party", required=True,
        states={
            'readonly': ((Eval('state') != 'draft')
                | (Eval('lines', [0]) & Eval('party'))),
            },
        depends=['state'],
        help="The party who subscribes.")
    invoice_address = fields.Many2One(
        'party.address', "Invoice Address",
        domain=[
            ('party', '=', Eval('party')),
            ],
        states={
            'readonly': Eval('state') != 'draft',
            'required': ~Eval('state').in_(['draft']),
            },
        depends=['party', 'state'])
    payment_term = fields.Many2One(
        'account.invoice.payment_term', "Payment Term",
        states={
            'readonly': Eval('state') != 'draft',
            },
        depends=['state'])

    currency = fields.Many2One(
        'currency.currency', "Currency", required=True,
        states={
            'readonly': ((Eval('state') != 'draft')
                | (Eval('lines', [0]) & Eval('currency', 0))),
            },
        depends=['state'])

    start_date = fields.Date(
        "Start Date", required=True,
        states={
            'readonly': ((Eval('state') != 'draft')
                | Eval('next_invoice_date')),
            },
        depends=['state', 'next_invoice_date'])
    end_date = fields.Date(
        "End Date",
        domain=['OR',
            ('end_date', '>=', If(
                    Bool(Eval('start_date')),
                    Eval('start_date', datetime.date.min),
                    datetime.date.min)),
            ('end_date', '=', None),
            ],
        states={
            'readonly': Eval('state') != 'draft',
            },
        depends=['start_date', 'state'])

    invoice_recurrence = fields.Many2One(
        'sale.subscription.recurrence.rule.set', "Invoice Recurrence",
        required=True,
        states={
            'readonly': Eval('state') != 'draft',
            },
        depends=['state'])
    invoice_start_date = fields.Date("Invoice Start Date",
        states={
            'readonly': ((Eval('state') != 'draft')
                | Eval('next_invoice_date')),
            },
        depends=['state', 'next_invoice_date'])
    next_invoice_date = fields.Date("Next Invoice Date", readonly=True)

    lines = fields.One2Many(
        'sale.subscription.line', 'subscription', "Lines",
        states={
            'readonly': Eval('state') != 'draft',
            },
        depends=['state'])

    state = fields.Selection(
        STATES, "State", readonly=True, required=True,
        help="The current state of the subscription.")

    @classmethod
    def __setup__(cls):
        super(Subscription, cls).__setup__()
        cls._order = [
            ('start_date', 'DESC'),
            ('id', 'DESC'),
            ]
        cls._transitions |= set((
                ('draft', 'canceled'),
                ('draft', 'quotation'),
                ('quotation', 'canceled'),
                ('quotation', 'draft'),
                ('quotation', 'running'),
                ('running', 'draft'),
                ('running', 'closed'),
                ('canceled', 'draft'),
                ))
        cls._buttons.update({
                'cancel': {
                    'invisible': ~Eval('state').in_(['draft', 'quotation']),
                    'icon': 'tryton-cancel',
                    },
                'draft': {
                    'invisible': Eval('state').in_(['draft', 'closed']),
                    'icon': If(Eval('state') == 'canceled',
                        'tryton-clear', 'tryton-go-previous'),
                    },
                'quote': {
                    'invisible': Eval('state') != 'draft',
                    'readonly': ~Eval('lines', []),
                    'icon': 'tryton-go-next',
                    },
                'run': {
                    'invisible': Eval('state') != 'quotation',
                    'icon': 'tryton-go-next',
                    },
                })

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

    @classmethod
    def default_currency(cls):
        pool = Pool()
        Company = pool.get('company.company')
        company = cls.default_company()
        if company:
            return Company(company).currency.id

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

    @fields.depends('party')
    def on_change_party(self):
        self.invoice_address = None
        if self.party:
            self.invoice_address = self.party.address_get(type='invoice')
            self.payment_term = self.party.customer_payment_term

    @classmethod
    def set_number(cls, subscriptions):
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Config = pool.get('sale.configuration')

        config = Config(1)
        for subscription in subscriptions:
            if subscription.number:
                continue
            subscription.number = Sequence.get_id(
                config.subscription_sequence.id)
        cls.save(subscriptions)

    def compute_next_invoice_date(self):
        start_date = self.invoice_start_date or self.start_date
        date = self.next_invoice_date or self.start_date
        rruleset = self.invoice_recurrence.rruleset(start_date)
        dt = datetime.datetime.combine(date, datetime.time())
        inc = (start_date == date) and not self.next_invoice_date
        next_date = rruleset.after(dt, inc=inc)
        return next_date.date()

    @classmethod
    def copy(cls, subscriptions, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('state', 'draft')
        default.setdefault('number')
        default.setdefault('next_invoice_date')
        return super(Subscription, cls).copy(subscriptions, default=default)

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

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

    @classmethod
    @ModelView.button
    @Workflow.transition('quotation')
    def quote(cls, subscriptions):
        cls.set_number(subscriptions)

    @classmethod
    @ModelView.button
    @Workflow.transition('running')
    def run(cls, subscriptions):
        pool = Pool()
        Line = pool.get('sale.subscription.line')
        lines = []
        for subscription in subscriptions:
            if not subscription.next_invoice_date:
                subscription.next_invoice_date = (
                    subscription.compute_next_invoice_date())
            for line in subscription.lines:
                if (line.next_consumption_date is None
                        and not line.consumed):
                    line.next_consumption_date = (
                        line.compute_next_consumption_date())
            lines.extend(subscription.lines)
        Line.save(lines)
        cls.save(subscriptions)

    @classmethod
    def process(cls, subscriptions):
        to_close = []
        for subscription in subscriptions:
            if all(l.next_consumption_date is None
                    for l in subscription.lines):
                to_close.append(subscription)
        cls.close(to_close)

    @classmethod
    @Workflow.transition('closed')
    def close(cls, subscriptions):
        pass

    @classmethod
    def generate_invoice(cls, date=None):
        pool = Pool()
        Date = pool.get('ir.date')
        Consumption = pool.get('sale.subscription.line.consumption')
        Invoice = pool.get('account.invoice')
        InvoiceLine = pool.get('account.invoice.line')

        if date is None:
            date = Date.today()

        consumptions = Consumption.search([
                ('invoice_line', '=', None),
                ('line.subscription.next_invoice_date', '<=', date),
                ('line.subscription.state', 'in', ['running', 'closed']),
                ],
            order=[
                ('line.subscription.id', 'DESC'),
                ])

        def keyfunc(consumption):
            return consumption.line.subscription
        invoices = {}
        lines = {}
        for subscription, consumptions in groupby(consumptions, key=keyfunc):
            invoices[subscription] = invoice = subscription._get_invoice()
            lines[subscription] = Consumption.get_invoice_lines(
                consumptions, invoice)

        all_invoices = invoices.values()
        Invoice.save(all_invoices)

        all_invoice_lines = []
        for subscription, invoice in invoices.iteritems():
            invoice_lines, _ = lines[subscription]
            for line in invoice_lines:
                line.invoice = invoice
            all_invoice_lines.extend(invoice_lines)
        InvoiceLine.save(all_invoice_lines)

        all_consumptions = []
        for values in lines.itervalues():
            for invoice_line, consumptions in zip(*values):
                for consumption in consumptions:
                    assert not consumption.invoice_line
                    consumption.invoice_line = invoice_line
                    all_consumptions.append(consumption)
        Consumption.save(all_consumptions)

        Invoice.update_taxes(all_invoices)

        subscriptions = cls.search([
                ('next_invoice_date', '<=', date),
                ])
        for subscription in subscriptions:
            if subscription.state == 'running':
                while subscription.next_invoice_date <= date:
                    subscription.next_invoice_date = (
                        subscription.compute_next_invoice_date())
            else:
                subscription.next_invoice_date = None
        cls.save(subscriptions)

    def _get_invoice(self):
        pool = Pool()
        Invoice = pool.get('account.invoice')
        invoice = Invoice(
            company=self.company,
            type='out',
            party=self.party,
            invoice_address=self.invoice_address,
            currency=self.currency,
            account=self.party.account_receivable,
            )
        invoice.on_change_type()
        invoice.payment_term = self.payment_term
        return invoice
Пример #3
0
class Group(metaclass=PoolMeta):
    __name__ = 'account.payment.group'
    sepa_messages = fields.One2Many('account.payment.sepa.message',
                                    'origin',
                                    'SEPA Messages',
                                    readonly=True,
                                    domain=[('company', '=',
                                             Eval('company', -1))],
                                    states={
                                        'invisible': ~Eval('sepa_messages'),
                                    },
                                    depends=['company'])

    @classmethod
    def __setup__(cls):
        super(Group, cls).__setup__()
        cls._buttons.update({
            'generate_message': {},
        })

    def get_sepa_template(self):
        if self.kind == 'payable':
            return loader.load('%s.xml' % self.journal.sepa_payable_flavor)
        elif self.kind == 'receivable':
            return loader.load('%s.xml' % self.journal.sepa_receivable_flavor)

    def process_sepa(self):
        pool = Pool()
        Payment = pool.get('account.payment')
        if self.kind == 'receivable':
            mandates = Payment.get_sepa_mandates(self.payments)
            for payment, mandate in zip(self.payments, mandates):
                if not mandate:
                    raise ProcessError(
                        gettext(
                            'account_payment_sepa'
                            '.msg_payment_process_no_mandate',
                            payment=payment.rec_name))
                # Write one by one because mandate.sequence_type must be
                # recomputed each time
                Payment.write(
                    [payment], {
                        'sepa_mandate': mandate,
                        'sepa_mandate_sequence_type': mandate.sequence_type,
                    })
        else:
            for payment in self.payments:
                if not payment.sepa_bank_account_number:
                    raise ProcessError(
                        gettext(
                            'account_payment_sepa'
                            '.msg_payment_process_no_iban',
                            payment=payment.rec_name))
        self.generate_message(_save=False)

    @dualmethod
    @ModelView.button
    def generate_message(cls, groups, _save=True):
        pool = Pool()
        Message = pool.get('account.payment.sepa.message')
        for group in groups:
            tmpl = group.get_sepa_template()
            if not tmpl:
                raise NotImplementedError
            if not group.sepa_messages:
                group.sepa_messages = ()
            message = tmpl.generate(
                group=group,
                datetime=datetime,
                normalize=unicodedata.normalize,
            ).filter(remove_comment).render()
            message = Message(message=message,
                              type='out',
                              state='waiting',
                              company=group.company)
            group.sepa_messages += (message, )
        if _save:
            cls.save(groups)

    @property
    def sepa_initiating_party(self):
        return self.company.party

    def sepa_group_payment_key(self, payment):
        key = (('date', payment.date), )
        if self.kind == 'receivable':
            key += (('sequence_type', payment.sepa_mandate_sequence_type), )
            key += (('scheme', payment.sepa_mandate.scheme), )
        return key

    def sepa_group_payment_id(self, key):
        payment_id = str(key['date'].toordinal())
        if self.kind == 'receivable':
            payment_id += '-' + key['sequence_type']
        return payment_id

    @property
    def sepa_payments(self):
        pool = Pool()
        Payment = pool.get('account.payment')
        keyfunc = self.sepa_group_payment_key
        # re-browse to align cache
        payments = Payment.browse(sorted(self.payments, key=keyfunc))
        for key, grouped_payments in groupby(payments, key=keyfunc):
            yield dict(key), list(grouped_payments)
Пример #4
0
class PaymentTermLine(ModelSQL, ModelView):
    'Payment Term Line'
    __name__ = 'account.invoice.payment_term.line'
    sequence = fields.Integer('Sequence',
                              help='Use to order lines in ascending order')
    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)
    percentage = fields.Numeric(
        'Percentage',
        digits=(16, 8),
        states={
            'invisible': ~Eval('type').in_(['percent', 'percent_on_total']),
            'required': Eval('type').in_(['percent', 'percent_on_total']),
        },
        depends=['type'])
    divisor = fields.Numeric(
        'Divisor',
        digits=(16, 8),
        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.relativedelta', 'line', 'Deltas')

    @classmethod
    def __setup__(cls):
        super(PaymentTermLine, cls).__setup__()
        cls._order.insert(0, ('sequence', 'ASC'))
        cls._error_messages.update({
            'invalid_percentage_and_divisor':
            ('Percentage and '
             'Divisor values are not consistent in line "%(line)s" '
             'of payment term "%(term)s".'),
        })

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

        # Migration from 1.0 percent change into percentage
        if table.column_exist('percent'):
            cursor.execute(*sql_table.update(columns=[sql_table.percentage],
                                             values=[sql_table.percent * 100]))
            table.drop_column('percent', exception=True)

        # Migration from 2.2
        if table.column_exist('delay'):
            cursor.execute(
                *sql_table.update(columns=[sql_table.day],
                                  values=[31],
                                  where=sql_table.delay == 'end_month'))
            table.drop_column('delay', exception=True)
            lines = cls.search([])
            for line in lines:
                if line.percentage:
                    cls.write(
                        [line], {
                            'divisor':
                            cls.round(
                                Decimal('100.0') / line.percentage,
                                cls.divisor.digits[1]),
                        })

        # 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 == Null, table.sequence]

    @staticmethod
    def default_currency_digits():
        return 2

    @staticmethod
    def default_type():
        return 'remainder'

    @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.percentage = Decimal('0.0')
            self.divisor = Decimal('0.0')

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

    @fields.depends('divisor')
    def on_change_divisor(self):
        if not self.divisor:
            self.percentage = 0.0
        else:
            self.percentage = self.round(
                Decimal('100.0') / self.divisor,
                self.__class__.percentage.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_delta(self):
        return {
            'day': self.day,
            'month': int(self.month) if self.month else None,
            'days': self.days,
            'weeks': self.weeks,
            'months': self.months,
            'weekday': int(self.weekday) if self.weekday else None,
        }

    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':
            return Currency.compute(self.currency, self.amount, currency)
        elif self.type == 'percent':
            return currency.round(remainder * self.percentage / Decimal('100'))
        elif self.type == 'percent_on_total':
            return currency.round(amount * self.percentage / Decimal('100'))
        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_percentage_and_divisor(lines)

    @classmethod
    def check_percentage_and_divisor(cls, lines):
        "Check consistency between percentage and divisor"
        percentage_digits = cls.percentage.digits[1]
        divisor_digits = cls.divisor.digits[1]
        for line in lines:
            if line.type not in ('percent', 'percent_on_total'):
                continue
            if line.percentage is None or line.divisor is None:
                cls.raise_user_error('invalid_percentage_and_divisor', {
                    'line': line.rec_name,
                    'term': line.payment.rec_name,
                })
            if line.percentage == line.divisor == Decimal('0.0'):
                continue
            percentage = line.percentage
            divisor = line.divisor
            calc_percentage = cls.round(
                Decimal('100.0') / divisor, percentage_digits)
            calc_divisor = cls.round(
                Decimal('100.0') / percentage, divisor_digits)
            if (percentage == Decimal('0.0') or divisor == Decimal('0.0') or
                    percentage != calc_percentage and divisor != calc_divisor):
                cls.raise_user_error('invalid_percentage_and_divisor', {
                    'line': line.rec_name,
                    'term': line.payment.rec_name,
                })
Пример #5
0
class LotAttributeType(ModelSQL, ModelView):
    'Lot Attribute Type'
    __name__ = 'stock.lot.attribute.type'

    name = fields.Char('Name', required=True)
    attributes = fields.One2Many('stock.lot.attribute', 'type', 'Attributes')
Пример #6
0
class Receipt(Workflow, ModelSQL, ModelView):
    "Cash/Bank Receipt"
    __name__ = "cash_bank.receipt"

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

    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)
    cash_bank = fields.Many2One(
        'cash_bank.cash_bank', 'Cash/Bank', required=True,
        domain=[
            ('company', 'in',
                [Eval('context', {}).get('company', -1), None])
        ],
        states={
            'readonly': Bool(Eval('lines')) | Bool(Eval('state') != 'draft')
        }, depends=_depends + ['lines'])
    type = fields.Many2One('cash_bank.receipt_type', 'Type', required=True,
        domain=[
            If(Bool(Eval('cash_bank')),
                [('cash_bank', '=', Eval('cash_bank'))],
                [('id', '=', -1)]
                ),
            ],
        states={
            'readonly': Bool(Eval('lines')) | Bool(Eval('state') != 'draft')
        }, depends=_depends + ['cash_bank', 'lines'])
    type_type = fields.Function(fields.Char('Type of Cash/Bank type',
        size=None), 'on_change_with_type_type')
    currency = fields.Many2One('currency.currency', 'Currency', required=True,
        states={'readonly': True})
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
        'on_change_with_currency_digits')
    number = fields.Char('Number', size=None, readonly=True, select=True)
    reference = fields.Char('Reference', size=None)
    description = fields.Char('Description', size=None,
        states=_states, depends=_depends)
    date = fields.Date('Date', required=True,
        states=_states, depends=_depends)
    party = fields.Many2One('party.party', 'Party', ondelete='RESTRICT',
        states={
            'readonly': Eval('state') != 'draft',
            'required': Bool(Eval('party_required'))
        }, depends=_depends + ['party_required'])
    party_required = fields.Function(fields.Boolean('Party Required'),
        'on_change_with_party_required')
    bank_account = fields.Many2One('bank.account', 'Bank Account',
        states={
            'readonly': Eval('state') != 'draft',
            'invisible': Not(Bool(Eval('bank_account_show'))),
            'required': Bool(Eval('bank_account_required'))
        },
        domain=[
            ('id', 'in', Eval('bank_account_owners'))
        ], depends=_depends + ['party', 'bank_account_show',
            'bank_account_owners', 'bank_account_required'])
    bank_account_show = fields.Function(fields.Boolean('Bank Account Show'),
        'on_change_with_bank_account_show')
    bank_account_owners = fields.Function(fields.One2Many('bank.account',
        None, 'Bank Account Owners'),
        'on_change_with_bank_account_owners',
        setter='set_bank_account_owners')
    bank_account_required = fields.Function(fields.Boolean(
        'Bank Account Required'),
        'on_change_with_bank_account_required')
    cash = fields.Numeric('Cash',
        digits=(16, Eval('_parent_receipt', {}).get('currency_digits', 2)),
        states=_states, depends=_depends + ['currency_digits'])
    documents = fields.Many2Many('cash_bank.document-cash_bank.receipt',
        'receipt', 'document', 'Documents',
        domain=[
            If(Eval('state') != 'posted',
                [
                    [('convertion', '=', None)],
                    If(Bool(Eval('type')),
                        If(Eval('type_type') == 'in',
                            ['OR',
                                [('last_receipt', '=', None)],
                                [('last_receipt.id', '=', Eval('id'))],
                                [('last_receipt.type.type', '=', 'out')]
                            ],
                            ['OR',
                                [('last_receipt.id', '=', Eval('id'))],
                                [
                                    ('last_receipt.type.type', '=', 'in'),
                                    ('last_receipt.state', 'in',
                                        ['confirmed', 'posted'])
                                ]
                            ],
                        ),
                        [('id', '=', -1)]
                    ),
                ],
                [('id', '!=', -1)]
            )
        ],
        states=_states,
        depends=_depends + ['id', 'type', 'type_type'])
    total_documents = fields.Function(fields.Numeric('Total Documents',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
        'get_total_detail')
    document_allow = fields.Function(fields.Boolean('Allow documents'),
        'on_change_with_document_allow')
    lines = fields.One2Many('cash_bank.receipt.line', 'receipt',
        'Lines', states={
            'readonly': (Not(Bool(Eval('cash_bank')))
                            | Not(Bool(Eval('type')))
                            | Bool(Eval('state') != 'draft'))
        },
        depends=_depends + ['cash_bank', 'type'])
    total_lines = fields.Function(fields.Numeric('Total Lines',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
        'get_total_detail')
    total = fields.Function(fields.Numeric('Total',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
        'get_total')
    diff = fields.Function(fields.Numeric('Diff',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
        'get_diff')
    move = fields.Many2One('account.move', 'Move', readonly=True,
        domain=[
            ('company', '=', Eval('company', -1)),
        ],
        depends=['company'])
    line_move = fields.Many2One('account.move.line', 'Account Move Line',
        readonly=True)
    state = fields.Selection([
        ('draft', 'Draft'),
        ('confirmed', 'Confirmed'),
        ('posted', 'Posted'),
        ('cancel', 'Canceled'),
        ], 'State', readonly=True, required=True)
    transfer = fields.Many2One('cash_bank.transfer', 'Transfer',
        readonly=True)
    attachments = fields.One2Many('ir.attachment', 'resource', 'Attachments')
    logs = fields.One2Many('cash_bank.receipt.log_action', 'resource',
        'Logs', readonly=True)

    del _states, _depends

    @classmethod
    def __register__(cls, module_name):
        super(Receipt, cls).__register__(module_name)
        table = cls.__table_handler__(module_name)
        # Migration from 5.2.1:
        if table.column_exist('made_by'):
            cls._migrate_log()
            table.drop_column('made_by')
            table.drop_column('confirmed_by')
            table.drop_column('posted_by')
            table.drop_column('canceled_by')

    @classmethod
    def _migrate_log(cls):
        def add_log(Log, User, receipt, user, action, logs, create=False):
            if not user:
                return
            user = User(user)
            if create:
                date = receipt.create_date
            else:
                date = receipt.write_date
            log = Log(
                resource=receipt,
                date=date,
                user=user,
                action=action
            )
            logs.append(log)

        pool = Pool()
        Log = pool.get('cash_bank.receipt.log_action')
        User = pool.get('res.user')

        logs = []

        cursor = Transaction().connection.cursor()
        sql = "SELECT id, made_by, confirmed_by, canceled_by, posted_by " \
            "FROM cash_bank_receipt"
        cursor.execute(sql)
        records = cursor.fetchall()
        for row in records:
            rcp = cls(row[0])
            add_log(Log, User, rcp, row[1], 'Created', logs, True)
            add_log(Log, User, rcp, row[2], 'Confirmed', logs)
            add_log(Log, User, rcp, row[4], 'Posted', logs)
            add_log(Log, User, rcp, row[3], 'Cancelled', logs)
        Log.save(logs)

    @classmethod
    def __setup__(cls):
        super(Receipt, cls).__setup__()
        cls._order = [
                ('date', 'DESC'),
                ('number', 'DESC'),
                ]

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

        cls._buttons.update({
            'cancel': {
                'invisible': ~Eval('state').in_(['confirmed']),
                'readonly': Bool(Eval('transfer')),
                },
            'confirm': {
                'invisible': ~Eval('state').in_(['draft']),
                },
            'post': {
                'invisible': ~Eval('state').in_(['confirmed']),
                'readonly': Bool(Eval('transfer')),
                },
            '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_lines():
        return Decimal('0.0')

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

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

    @staticmethod
    def default_diff():
        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('type')
    def on_change_with_type_type(self, name=None):
        if self.type:
            return self.type.type

    @fields.depends('type', 'documents', 'state')
    def on_change_with_document_allow(self, name=None):
        if self.type:
            if self.type.type == 'in':
                return True
            else:
                if self.documents and self.state != 'draft':
                    return True

    @fields.depends('type')
    def on_change_with_party_required(self, name=None):
        if self.type:
            return self.type.party_required

    @fields.depends('type', 'bank_account_show')
    def on_change_with_bank_account_required(self, name=None):
        if self.type and self.bank_account_show:
            if self.bank_account_show == True:
                return self.type.bank_account_required

    @fields.depends('type', 'party_required')
    def on_change_with_bank_account_show(self, name=None):
        if self.type and self.party_required:
            if self.party_required == True:
                return self.type.bank_account

    @fields.depends('type', 'party')
    def on_change_with_bank_account_owners(self, name=None):
        if self.type and self.party:
            if self.party.bank_accounts:
                res = []
                for acc in self.party.bank_accounts:
                    res.append(acc.id)
                return res
        return []

    @classmethod
    def set_bank_account_owners(cls, lines, name, value):
        pass

    @fields.depends()
    def on_change_company(self):
        self.cash_bank = None
        self.type = None

    @fields.depends()
    def on_change_cash_bank(self):
        self.type = None

    @fields.depends('type')
    def on_change_type(self):
        if self.type:
            self.cash_bank = self.type.cash_bank

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

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

    @fields.depends('lines', 'cash', 'total_documents')
    def on_change_lines(self):
        self.total_lines = \
            self.get_total_detail('total_lines')
        self._set_total(
            self.total_documents, self.total_lines)

    def get_total_detail(self, name):
        name = name[6:]  # Remove 'total_' from begining
        total = self._get_total_details(getattr(self, name))
        return total

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

    def get_diff(self, name=None):
        return self.total_lines - self.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 view_attributes(cls):
        return super(Receipt, cls).view_attributes() + [
            ('//page[@name="documents"]', 'states', {
                    'invisible': ~Eval('document_allow'),
                    })]

    def _get_total_details(self, details):
        result = Decimal('0.0')
        if details:
            for detail in details:
                if detail.amount:
                    result += detail.amount
        return result

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

    def _get_move(self):
        'Return Move for Receipt'
        pool = Pool()
        Move = pool.get('account.move')
        Period = pool.get('account.period')
        period_id = Period.find(self.company.id, date=self.date)
        move = Move(
            period=period_id,
            journal=self.cash_bank.journal_cash_bank,
            date=self.date,
            origin=self,
            company=self.company,
            description=self.description,
            )
        return move, period_id

    def _get_move_line(self, period):
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        Currency = pool.get('currency.currency')
        debit = Decimal('0.0')
        credit = Decimal('0.0')

        with Transaction().set_context(date=self.date):
            amount = Currency.compute(self.currency,
                self.total, self.company.currency)
        if self.currency != self.company.currency:
            second_currency = self.currency
            amount_second_currency = self.total
        else:
            amount_second_currency = None
            second_currency = None

        if self.type.type == 'in':
            debit = amount
        else:
            credit = amount

        description = self.description
        if self.reference:
            if description:
                description += ' / '
            description += self.reference

        return MoveLine(
            period=period,
            debit=debit,
            credit=credit,
            account=self.cash_bank.account,
            second_currency=second_currency,
            amount_second_currency=amount_second_currency,
            description=description,
            )

    @classmethod
    def create(cls, vlist):
        receipts = super(Receipt, cls).create(vlist)
        write_log('log_action.msg_created', receipts)
        return receipts

    @classmethod
    def validate(cls, receipts):
        super(Receipt, cls).validate(receipts)
        cls.set_document_receipt(receipts)

    @classmethod
    def set_document_receipt(cls, receipts):
        def doc_exists(id_, docs):
            for dc in docs:
                if dc.id == id_:
                    return True

        Document = Pool().get('cash_bank.document')

        lasts = {}
        for receipt in receipts:
            for doc in receipt.documents:
                if doc.last_receipt != receipt:
                    doc.last_receipt = receipt
                    doc.save()
                    if receipt.transfer and \
                            receipt.transfer.state in ['confirmed', 'post']:
                        pass
                    else:
                        if receipt.rec_name not in lasts:
                            lasts[receipt.rec_name] = []
                        lasts[receipt.rec_name].append(doc)

            # Verify if any document have been deleted from list
            # so last_receipt must be updated
            documents = Document.search([
                ('last_receipt', '=', receipt.id)])
            for doc in documents:
                if not doc_exists(doc.id, receipt.documents):
                    doc.set_previous_receipt()
                    doc.save()

        for key, value in lasts.items():
            write_log('Asigned to Receipt: ' + key, value)

    @classmethod
    def set_number(cls, receipts):
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        for receipt in receipts:
            if receipt.number:
                continue
            receipt.number = \
                Sequence.get_id(receipt.type.sequence.id)
        cls.save(receipts)

    @classmethod
    def delete(cls, receipts):
        pool = Pool()
        Attachment = pool.get('ir.attachment')

        atts = []
        for receipt in receipts:
            if receipt.state not in ['draft']:
                write_log('log_action.msg_deletion_attempt', [receipt])
                raise UserError(
                    gettext('cash_bank.msg_delete_document_cash_bank',
                        doc_name='Receipt',
                        doc_number=receipt.rec_name,
                        state='Draft'
                    ))
            for doc in receipt.documents:
                doc.set_previous_receipt()
                doc.save()
            for att in receipt.attachments:
                atts.append(att)

        Attachment.delete(atts)
        super(Receipt, cls).delete(receipts)

    @classmethod
    @ModelView.button
    @Workflow.transition('draft')
    def draft(cls, receipts):
        write_log('log_action.msg_draft', receipts)

    @classmethod
    @ModelView.button
    @Workflow.transition('confirmed')
    def confirm(cls, receipts):
        for receipt in receipts:
            if not receipt.lines:
                raise UserError(
                    gettext('cash_bank.msg_receipt_no_lines',
                    receipt=receipt.rec_name
                    ))
            if receipt.diff != 0:
                raise UserError(
                    gettext('cash_bank.msg_diff_total_lines_cash_bank'
                    ))
            if receipt.total < 0:
                raise UserError(
                    gettext('cash_bank.msg_total_less_zero'
                    ))
            if receipt.cash < 0:
                raise UserError(
                    gettext('cash_bank.msg_cash_less_zero'
                    ))
            for doc in receipt.documents:
                if doc.amount <= 0:
                    raise UserError(
                        gettext('cash_bank.msg_document_less_equal_zero'
                        ))
            if receipt.type.party_required and not receipt.party:
                raise UserError(
                    gettext('cash_bank.msg_party_required_cash_bank'
                    ))
            move, period = receipt._get_move()
            move.save()
            receipt_line_move = receipt._get_move_line(period)
            receipt_line_move.move = move
            receipt_line_move.save()
            move_lines = [receipt_line_move]
            for line in receipt.lines:
                line.validate_line()
                move_line = line.get_move_line(period)
                move_line.move = move
                move_line.save()
                line.line_move = move_line
                line.save()
                move_lines.append(move_line)
            move.lines = move_lines
            move.save()

            receipt.move = move
            receipt.line_move = receipt_line_move
            receipt.save()

        cls.set_number(receipts)
        write_log('log_action.msg_confirmed', receipts)

    @classmethod
    @ModelView.button
    @Workflow.transition('posted')
    def post(cls, receipts):
        Move = Pool().get('account.move')

        for receipt in receipts:
            for line in receipt.lines:
                line.reconcile()

        Move.post([r.move for r in receipts])
        write_log('log_action.msg_posted', receipts)

    @classmethod
    @ModelView.button
    @Workflow.transition('cancel')
    def cancel(cls, receipts):
        Move = Pool().get('account.move')
        Move.delete([r.move for r in receipts])
        write_log('log_action.msg_cancelled', receipts)
Пример #7
0
class Author(ModelSQL, ModelView):
    'Author'
    __name__ = 'library.author'

    books = fields.One2Many('library.book', 'author', 'Books')
    name = fields.Char('Name', required=True)
    birth_date = fields.Date(
        'Birth date',
        states={'required': Bool(Eval('death_date', 'False'))},
        depends=['death_date'])
    death_date = fields.Date('Death date',
                             domain=[
                                 'OR', ('death_date', '=', None),
                                 ('death_date', '>', Eval('birth_date'))
                             ],
                             states={'invisible': ~Eval('birth_date')},
                             depends=['birth_date'])
    gender = fields.Selection([('man', 'Man'), ('woman', 'Woman')], 'Gender')
    age = fields.Function(
        fields.Integer('Age', states={'invisible': ~Eval('death_date')}),
        'on_change_with_age')
    number_of_books = fields.Function(fields.Integer('Number of books'),
                                      'getter_number_of_books')
    genres = fields.Function(fields.Many2Many(
        'library.genre',
        None,
        None,
        'Genres',
        states={'invisible': ~Eval('books', False)}),
                             'getter_genres',
                             searcher='searcher_genres')
    latest_book = fields.Function(
        fields.Many2One('library.book',
                        'Latest Book',
                        states={'invisible': ~Eval('books', False)}),
        'getter_latest_book')

    @fields.depends('birth_date')
    def on_change_birth_date(self):
        if not self.birth_date:
            self.death_date = None

    @fields.depends('books')
    def on_change_books(self):
        if not self.books:
            self.genres = []
            self.number_of_books = 0
            return
        self.number_of_books, genres = 0, set()
        for book in self.books:
            self.number_of_books += 1
            if book.genre:
                genres.add(book.genre)
        self.genres = list(genres)

    @fields.depends('birth_date', 'death_date')
    def on_change_with_age(self, name=None):
        if not self.birth_date:
            return None
        end_date = self.death_date or datetime.date.today()
        age = end_date.year - self.birth_date.year
        if (end_date.month, end_date.day) < (self.birth_date.month,
                                             self.birth_date.day):
            age -= 1
        return age

    def getter_genres(self, name):
        genres = set()
        for book in self.books:
            if book.genre:
                genres.add(book.genre.id)
        return list(genres)

    @classmethod
    def getter_latest_book(cls, authors, name):
        result = {x.id: None for x in authors}
        Book = Pool().get('library.book')
        book = Book.__table__()
        sub_book = Book.__table__()
        cursor = Transaction().connection.cursor()

        sub_query = sub_book.select(
            sub_book.author,
            Max(Coalesce(sub_book.publishing_date, datetime.date.min),
                window=Window([sub_book.author])).as_('max_date'),
            where=sub_book.author.in_([x.id for x in authors]))

        cursor.execute(
            *book.join(sub_query,
                       condition=(book.author == sub_query.author)
                       & (Coalesce(book.publishing_date, datetime.date.min) ==
                          sub_query.max_date)).select(book.author, book.id))
        for author_id, book in cursor.fetchall():
            result[author_id] = book
        return result

    @classmethod
    def getter_number_of_books(cls, authors, name):
        result = {x.id: 0 for x in authors}
        Book = Pool().get('library.book')
        book = Book.__table__()

        cursor = Transaction().connection.cursor()
        cursor.execute(
            *book.select(book.author,
                         Count(book.id),
                         where=book.author.in_([x.id for x in authors]),
                         group_by=[book.author]))
        for author_id, count in cursor.fetchall():
            result[author_id] = count
        return result

    @classmethod
    def searcher_genres(cls, name, clause):
        return []
Пример #8
0
class BoardLaboratory(metaclass=PoolMeta):
    __name__ = 'lims.board.laboratory'

    analysis_sheet_templates = fields.One2Many(
        'lims.board.laboratory.analysis_sheet_template', None,
        'Unplanned samples per Analysis sheet', states={'readonly': True})

    @ModelView.button_change('analysis_sheet_templates')
    def apply_filter(self):
        super().apply_filter()
        self.analysis_sheet_templates = self.get_analysis_sheet_templates()

    def get_analysis_sheet_templates(self):
        pool = Pool()
        TemplateAnalysisSheet = pool.get('lims.template.analysis_sheet')

        records = []

        with Transaction().set_context(
                date_from=self.date_from, date_to=self.date_to):
            templates = TemplateAnalysisSheet.browse(self._get_templates())
            for t in templates:
                record = {'t': t.name, 'q': t.pending_fractions}
                records.append(record)

        return records

    def _get_templates(self):
        cursor = Transaction().connection.cursor()
        pool = Pool()
        PlanificationServiceDetail = pool.get(
            'lims.planification.service_detail')
        PlanificationDetail = pool.get('lims.planification.detail')
        Planification = pool.get('lims.planification')
        NotebookLine = pool.get('lims.notebook.line')
        Notebook = pool.get('lims.notebook')
        Fraction = pool.get('lims.fraction')
        EntryDetailAnalysis = pool.get('lims.entry.detail.analysis')
        Analysis = pool.get('lims.analysis')
        Template = pool.get('lims.template.analysis_sheet')
        TemplateAnalysis = pool.get('lims.template.analysis_sheet.analysis')

        cursor.execute('SELECT nl.id '
            'FROM "' + NotebookLine._table + '" nl '
                'INNER JOIN "' + PlanificationServiceDetail._table +
                '" psd ON psd.notebook_line = nl.id '
                'INNER JOIN "' + PlanificationDetail._table + '" pd '
                'ON psd.detail = pd.id '
                'INNER JOIN "' + Planification._table + '" p '
                'ON pd.planification = p.id '
            'WHERE p.state = \'preplanned\'')
        planned_lines = [x[0] for x in cursor.fetchall()]
        planned_lines_ids = ', '.join(str(x) for x in [0] + planned_lines)
        preplanned_where = 'AND nl.id NOT IN (%s) ' % planned_lines_ids

        dates_where = ''
        if self.date_from:
            dates_where += ('AND ad.confirmation_date::date >= \'%s\'::date ' %
                self.date_from)
        if self.date_to:
            dates_where += ('AND ad.confirmation_date::date <= \'%s\'::date ' %
                self.date_to)

        sql_select = 'SELECT nl.analysis, nl.method '
        sql_from = (
            'FROM "' + NotebookLine._table + '" nl '
            'INNER JOIN "' + Analysis._table + '" nla '
            'ON nla.id = nl.analysis '
            'INNER JOIN "' + Notebook._table + '" nb '
            'ON nb.id = nl.notebook '
            'INNER JOIN "' + Fraction._table + '" frc '
            'ON frc.id = nb.fraction '
            'INNER JOIN "' + EntryDetailAnalysis._table + '" ad '
            'ON ad.id = nl.analysis_detail ')
        sql_where = (
            'WHERE ad.plannable = TRUE '
            'AND nl.start_date IS NULL '
            'AND nl.annulled = FALSE '
            'AND nla.behavior != \'internal_relation\' ' +
            preplanned_where + dates_where)

        with Transaction().set_user(0):
            cursor.execute(sql_select + sql_from + sql_where)
        notebook_lines = cursor.fetchall()
        if not notebook_lines:
            return []

        result = []
        for nl in notebook_lines:
            cursor.execute('SELECT t.id '
                'FROM "' + Template._table + '" t '
                    'INNER JOIN "' + TemplateAnalysis._table + '" ta '
                    'ON t.id = ta.template '
                'WHERE t.active IS TRUE '
                    'AND ta.analysis = %s '
                    'AND (ta.method = %s OR ta.method IS NULL)',
                (nl[0], nl[1]))
            template = cursor.fetchone()
            if template:
                result.append(template[0])
        return list(set(result))
Пример #9
0
class Module(ModelSQL, ModelView):
    "Module"
    __name__ = "ir.module"
    name = fields.Char("Name", readonly=True, required=True)
    version = fields.Function(fields.Char('Version'), 'get_version')
    dependencies = fields.One2Many('ir.module.dependency',
                                   'module',
                                   'Dependencies',
                                   readonly=True)
    parents = fields.Function(fields.One2Many('ir.module', None, 'Parents'),
                              'get_parents')
    childs = fields.Function(fields.One2Many('ir.module', None, 'Childs'),
                             'get_childs')
    state = fields.Selection([
        ('not activated', 'Not Activated'),
        ('activated', 'Activated'),
        ('to upgrade', 'To be upgraded'),
        ('to remove', 'To be removed'),
        ('to activate', 'To be activated'),
    ],
                             string='State',
                             readonly=True)

    @classmethod
    def __setup__(cls):
        super(Module, cls).__setup__()
        table = cls.__table__()
        cls._sql_constraints = [
            ('name_uniq', Unique(table, table.name),
             'The name of the module must be unique!'),
        ]
        cls._order.insert(0, ('name', 'ASC'))
        cls.__rpc__.update({
            'on_write': RPC(instantiate=0),
        })
        cls._buttons.update({
            'activate': {
                'invisible': Eval('state') != 'not activated',
                'depends': ['state'],
            },
            'activate_cancel': {
                'invisible': Eval('state') != 'to activate',
                'depends': ['state'],
            },
            'deactivate': {
                'invisible': Eval('state') != 'activated',
                'depends': ['state'],
            },
            'deactivate_cancel': {
                'invisible': Eval('state') != 'to remove',
                'depends': ['state'],
            },
            'upgrade': {
                'invisible': Eval('state') != 'activated',
                'depends': ['state'],
            },
            'upgrade_cancel': {
                'invisible': Eval('state') != 'to upgrade',
                'depends': ['state'],
            },
        })

    @classmethod
    def __register__(cls, module_name):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        sql_table = cls.__table__()
        model_data_sql_table = ModelData.__table__()
        cursor = Transaction().connection.cursor()

        # Migration from 3.6: remove double module
        old_table = 'ir_module_module'
        if backend.TableHandler.table_exist(old_table):
            backend.TableHandler.table_rename(old_table, cls._table)

        super(Module, cls).__register__(module_name)

        # Migration from 4.0: rename installed to activated
        cursor.execute(*sql_table.update([sql_table.state], ['activated'],
                                         where=sql_table.state == 'installed'))
        cursor.execute(
            *sql_table.update([sql_table.state], ['not activated'],
                              where=sql_table.state == 'uninstalled'))

        # Migration from 4.6: register buttons on ir module
        button_fs_ids = [
            'module_activate_button',
            'module_activate_cancel_button',
            'module_deactivate_button',
            'module_deactivate_cancel_button',
            'module_upgrade_button',
            'module_upgrade_cancel_button',
        ]
        cursor.execute(*model_data_sql_table.update(
            [model_data_sql_table.module], ['ir'],
            where=((model_data_sql_table.module == 'res')
                   & (model_data_sql_table.fs_id.in_(button_fs_ids)))))

    @staticmethod
    def default_state():
        return 'not activated'

    def get_version(self, name):
        return get_module_info(self.name).get('version', '')

    @classmethod
    def get_parents(cls, modules, name):
        parent_names = list(
            set(d.name for m in modules for d in m.dependencies))
        parents = cls.search([
            ('name', 'in', parent_names),
        ])
        name2id = dict((m.name, m.id) for m in parents)
        return dict(
            (m.id, [name2id[d.name] for d in m.dependencies]) for m in modules)

    @classmethod
    def get_childs(cls, modules, name):
        child_ids = dict((m.id, []) for m in modules)
        name2id = dict((m.name, m.id) for m in modules)
        childs = cls.search([
            ('dependencies.name', 'in', list(name2id.keys())),
        ])
        for child in childs:
            for dep in child.dependencies:
                if dep.name in name2id:
                    child_ids[name2id[dep.name]].append(child.id)
        return child_ids

    @classmethod
    def delete(cls, records):
        for module in records:
            if module.state in (
                    'activated',
                    'to upgrade',
                    'to remove',
                    'to activate',
            ):
                raise AccessError(gettext('ir.msg_module_delete_state'))
        return super(Module, cls).delete(records)

    @classmethod
    def on_write(cls, modules):
        dependencies = set()

        def get_parents(module):
            parents = set(p.id for p in module.parents)
            for p in module.parents:
                parents.update(get_parents(p))
            return parents

        def get_childs(module):
            childs = set(c.id for c in module.childs)
            for c in module.childs:
                childs.update(get_childs(c))
            return childs

        for module in modules:
            dependencies.update(get_parents(module))
            dependencies.update(get_childs(module))
        return list(dependencies)

    @classmethod
    @ModelView.button
    @filter_state('not activated')
    def activate(cls, modules):
        modules_activated = set(modules)

        def get_parents(module):
            parents = set(p for p in module.parents)
            for p in module.parents:
                parents.update(get_parents(p))
            return parents

        for module in modules:
            modules_activated.update(
                (m for m in get_parents(module) if m.state == 'not activated'))
        cls.write(list(modules_activated), {
            'state': 'to activate',
        })

    @classmethod
    @ModelView.button
    @filter_state('activated')
    def upgrade(cls, modules):
        modules_activated = set(modules)

        def get_childs(module):
            childs = set(c for c in module.childs)
            for c in module.childs:
                childs.update(get_childs(c))
            return childs

        for module in modules:
            modules_activated.update(
                (m for m in get_childs(module) if m.state == 'activated'))
        cls.write(list(modules_activated), {
            'state': 'to upgrade',
        })

    @classmethod
    @ModelView.button
    @filter_state('to activate')
    def activate_cancel(cls, modules):
        cls.write(modules, {
            'state': 'not activated',
        })

    @classmethod
    @ModelView.button
    @filter_state('activated')
    def deactivate(cls, modules):
        pool = Pool()
        Module = pool.get('ir.module')
        Dependency = pool.get('ir.module.dependency')
        module_table = Module.__table__()
        dep_table = Dependency.__table__()
        cursor = Transaction().connection.cursor()
        for module in modules:
            cursor.execute(*dep_table.join(
                module_table, condition=(dep_table.module == module_table.id)
            ).select(
                module_table.state,
                module_table.name,
                where=(dep_table.name == module.name)
                & NotIn(module_table.state, ['not activated', 'to remove'])))
            res = cursor.fetchall()
            if res:
                raise DeactivateDependencyError(
                    gettext('ir.msg_module_deactivate_dependency'),
                    '\n'.join('\t%s: %s' % (x[0], x[1]) for x in res))
        cls.write(modules, {'state': 'to remove'})

    @classmethod
    @ModelView.button
    @filter_state('to remove')
    def deactivate_cancel(cls, modules):
        cls.write(modules, {'state': 'not activated'})

    @classmethod
    @ModelView.button
    @filter_state('to upgrade')
    def upgrade_cancel(cls, modules):
        cls.write(modules, {'state': 'activated'})

    @classmethod
    def update_list(cls):
        'Update the list of available packages'
        count = 0
        module_names = get_module_list()

        modules = cls.search([])
        name2module = dict((m.name, m) for m in modules)
        cls.delete([
            m for m in modules
            if m.state != 'activated' and m.name not in module_names
        ])

        # iterate through activated modules and mark them as being so
        for name in module_names:
            if name in name2module:
                module = name2module[name]
                tryton = get_module_info(name)
                cls._update_dependencies(module, tryton.get('depends', []))
                continue

            tryton = get_module_info(name)
            if not tryton:
                continue
            module, = cls.create([{
                'name': name,
                'state': 'not activated',
            }])
            count += 1
            cls._update_dependencies(module, tryton.get('depends', []))
        return count

    @classmethod
    def _update_dependencies(cls, module, depends=None):
        pool = Pool()
        Dependency = pool.get('ir.module.dependency')
        Dependency.delete(
            [x for x in module.dependencies if x.name not in depends])
        if depends is None:
            depends = []
        # Restart Browse Cache for deleted dependencies
        module = cls(module.id)
        dependency_names = [x.name for x in module.dependencies]
        to_create = []
        for depend in depends:
            if depend not in dependency_names:
                to_create.append({
                    'module': module.id,
                    'name': depend,
                })
        if to_create:
            Dependency.create(to_create)
Пример #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')
    to_reconcile = fields.Function(fields.Boolean("To Reconcile"),
                                   'get_to_reconcile')

    @classmethod
    def __setup__(cls):
        super(Statement, cls).__setup__()
        cls._order[0] = ('id', 'DESC')
        cls._transitions |= set((
            ('draft', 'validated'),
            ('draft', 'cancel'),
            ('validated', 'posted'),
            ('validated', 'cancel'),
            ('cancel', 'draft'),
        ))
        cls._buttons.update({
            'draft': {
                'invisible': Eval('state') != 'cancel',
                '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', 'cancel']),
                '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))

    @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('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 (getattr(line, 'invoice', None)
                    and line.invoice.currency == self.company.currency):
                invoices.add(line.invoice)
        for origin in self.origins:
            for line in origin.lines:
                if (getattr(line, 'invoice', None)
                        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 (getattr(line, 'invoice', None) 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 (getattr(line, 'invoice', None)
                    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 getattr(line, 'invoice', None) 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 [
            ('/tree', 'visual', If(Eval('state') == 'cancel', 'muted', '')),
        ]

    @classmethod
    def delete(cls, statements):
        # Cancel before delete
        cls.cancel(statements)
        for statement in statements:
            if statement.state != 'cancel':
                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')

        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)
            if Warning.check(warning_key):
                raise StatementValidateWarning(
                    warning_key,
                    gettext('account_statement'
                            '.msg_statement_paid_invoice_draft'))
            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])

        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))

        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)

    @classmethod
    @ModelView.button_action('account_statement.act_reconcile')
    def reconcile(cls, statements):
        pass
Пример #11
0
class Work:
    __metaclass__ = PoolMeta
    __name__ = 'project.work'
    project_invoice_method = fields.Selection(INVOICE_METHODS,
        'Invoice Method',
        states={
            'readonly': Bool(Eval('invoiced_duration')),
            'required': Eval('type') == 'project',
            'invisible': Eval('type') != 'project',
            },
        depends=['invoiced_duration', 'type'])
    invoice_method = fields.Function(fields.Selection(INVOICE_METHODS,
            'Invoice Method'), 'get_invoice_method')
    invoiced_duration = fields.Function(fields.TimeDelta('Invoiced Duration',
            'company_work_time',
            states={
                'invisible': Eval('invoice_method') == 'manual',
                },
            depends=['invoice_method']), 'get_total')
    duration_to_invoice = fields.Function(fields.TimeDelta(
            'Duration to Invoice', 'company_work_time',
            states={
                'invisible': Eval('invoice_method') == 'manual',
                },
            depends=['invoice_method']), 'get_total')
    invoiced_amount = fields.Function(fields.Numeric('Invoiced Amount',
            digits=(16, Eval('currency_digits', 2)),
            states={
                'invisible': Eval('invoice_method') == 'manual',
                },
            depends=['currency_digits', 'invoice_method']),
        'get_total')
    invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line',
        readonly=True)
    invoiced_progress = fields.One2Many('project.work.invoiced_progress',
        'work', 'Invoiced Progress', readonly=True)

    @classmethod
    def __setup__(cls):
        super(Work, cls).__setup__()
        cls._buttons.update({
                'invoice': {
                    'invisible': ((Eval('type') != 'project')
                        | (Eval('project_invoice_method', 'manual')
                            == 'manual')),
                    'readonly': ~Eval('duration_to_invoice'),
                    },
                })
        cls._error_messages.update({
                'missing_product': 'There is no product on work "%s".',
                'missing_list_price': 'There is no list price on work "%s".',
                'missing_party': 'There is no party on work "%s".',
                })

    @staticmethod
    def default_project_invoice_method():
        return 'manual'

    @classmethod
    def copy(cls, records, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default.setdefault('invoice_line', None)
        return super(Work, cls).copy(records, default=default)

    def get_invoice_method(self, name):
        if self.type == 'project':
            return self.project_invoice_method
        elif self.parent:
            return self.parent.invoice_method
        else:
            return 'manual'

    @staticmethod
    def default_invoiced_duration():
        return datetime.timedelta()

    @staticmethod
    def _get_invoiced_duration_manual(works):
        return {}

    @staticmethod
    def _get_invoiced_duration_effort(works):
        return dict((w.id, w.effort_duration) for w in works
            if w.invoice_line and w.effort_duration)

    @staticmethod
    def _get_invoiced_duration_progress(works):
        durations = {}
        for work in works:
            durations[work.id] = sum((p.effort_duration
                    for p in work.invoiced_progress if p.effort_duration),
                datetime.timedelta())
        return durations

    @classmethod
    def _get_invoiced_duration_timesheet(cls, works):
        return cls._get_duration_timesheet(works, True)

    @staticmethod
    def default_duration_to_invoice():
        return datetime.timedelta()

    @staticmethod
    def _get_duration_to_invoice_manual(works):
        return {}

    @staticmethod
    def _get_duration_to_invoice_effort(works):
        return dict((w.id, w.effort_duration) for w in works
            if w.state == 'done' and not w.invoice_line)

    @staticmethod
    def _get_duration_to_invoice_progress(works):
        durations = {}
        for work in works:
            if work.progress is None or work.effort_duration is None:
                continue
            effort_to_invoice = datetime.timedelta(
                hours=work.effort_hours * work.progress)
            effort_invoiced = sum(
                (p.effort_duration
                    for p in work.invoiced_progress),
                datetime.timedelta())
            if effort_to_invoice > effort_invoiced:
                durations[work.id] = effort_to_invoice - effort_invoiced
            else:
                durations[work.id] = datetime.timedelta()
        return durations

    @classmethod
    def _get_duration_to_invoice_timesheet(cls, works):
        return cls._get_duration_timesheet(works, False)

    @staticmethod
    def default_invoiced_amount():
        return Decimal(0)

    @staticmethod
    def _get_invoiced_amount_manual(works):
        return {}

    @staticmethod
    def _get_invoiced_amount_effort(works):
        pool = Pool()
        InvoiceLine = pool.get('account.invoice.line')
        Currency = pool.get('currency.currency')

        invoice_lines = InvoiceLine.browse([
                w.invoice_line.id for w in works
                if w.invoice_line])

        id2invoice_lines = dict((l.id, l) for l in invoice_lines)
        amounts = {}
        for work in works:
            currency = work.company.currency
            if work.invoice_line:
                invoice_line = id2invoice_lines[work.invoice_line.id]
                invoice_currency = (invoice_line.invoice.currency
                    if invoice_line.invoice else invoice_line.currency)
                amounts[work.id] = Currency.compute(invoice_currency,
                    Decimal(str(work.effort_hours)) * invoice_line.unit_price,
                    currency)
            else:
                amounts[work.id] = Decimal(0)
        return amounts

    @classmethod
    def _get_invoiced_amount_progress(cls, works):
        pool = Pool()
        Progress = pool.get('project.work.invoiced_progress')
        InvoiceLine = pool.get('account.invoice.line')
        Company = pool.get('company.company')
        Currency = pool.get('currency.currency')

        cursor = Transaction().connection.cursor()
        table = cls.__table__()
        progress = Progress.__table__()
        invoice_line = InvoiceLine.__table__()
        company = Company.__table__()

        amounts = defaultdict(Decimal)
        work2currency = {}
        work_ids = [w.id for w in works]
        for sub_ids in grouped_slice(work_ids):
            where = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.join(progress,
                    condition=progress.work == table.id
                    ).join(invoice_line,
                    condition=progress.invoice_line == invoice_line.id
                    ).select(table.id,
                    Sum(progress.effort_duration * invoice_line.unit_price),
                    where=where,
                    group_by=table.id))
            for work_id, amount in cursor.fetchall():
                if isinstance(amount, datetime.timedelta):
                    amount = amount.total_seconds()
                # Amount computed in second instead of hours
                if amount is not None:
                    amount /= 60 * 60
                else:
                    amount = 0
                amounts[work_id] = amount

            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.itervalues()))
        id2currency = {c.id: c for c in currencies}

        for work in works:
            currency = id2currency[work2currency[work.id]]
            amounts[work.id] = currency.round(Decimal(amounts[work.id]))
        return amounts

    @classmethod
    def _get_invoiced_amount_timesheet(cls, works):
        pool = Pool()
        TimesheetWork = pool.get('timesheet.work')
        TimesheetLine = pool.get('timesheet.line')
        InvoiceLine = pool.get('account.invoice.line')
        Company = pool.get('company.company')
        Currency = pool.get('currency.currency')

        cursor = Transaction().connection.cursor()
        table = cls.__table__()
        timesheet_work = TimesheetWork.__table__()
        timesheet_line = TimesheetLine.__table__()
        invoice_line = InvoiceLine.__table__()
        company = Company.__table__()

        amounts = {}
        work2currency = {}
        work_ids = [w.id for w in works]
        for sub_ids in grouped_slice(work_ids):
            where = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.join(timesheet_work,
                    condition=table.work == timesheet_work.id
                    ).join(timesheet_line,
                    condition=timesheet_line.work == timesheet_work.id
                    ).join(invoice_line,
                    condition=timesheet_line.invoice_line == invoice_line.id
                    ).select(table.id,
                    Sum(timesheet_line.duration * invoice_line.unit_price),
                    where=where,
                    group_by=table.id))
            amounts.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.itervalues()))
        id2currency = {c.id: c for c in currencies}

        for work in works:
            currency = id2currency[work2currency[work.id]]
            amount = amounts.get(work.id, 0)
            if isinstance(amount, datetime.timedelta):
                amount = amount.total_seconds()
            amount = amount / 60 / 60
            amounts[work.id] = currency.round(Decimal(str(amount)))
        return amounts

    @staticmethod
    def _get_duration_timesheet(works, invoiced):
        pool = Pool()
        TimesheetLine = pool.get('timesheet.line')
        cursor = Transaction().connection.cursor()
        line = TimesheetLine.__table__()

        durations = {}
        twork2work = dict((w.work.id, w.id) for w in works if w.work)
        ids = twork2work.keys()
        for sub_ids in grouped_slice(ids):
            red_sql = reduce_ids(line.work, sub_ids)
            if invoiced:
                where = line.invoice_line != Null
            else:
                where = line.invoice_line == Null
            cursor.execute(*line.select(line.work, Sum(line.duration),
                    where=red_sql & where,
                    group_by=line.work))
            for twork_id, duration in cursor.fetchall():
                if duration:
                    # SQLite uses float for SUM
                    if not isinstance(duration, datetime.timedelta):
                        duration = datetime.timedelta(seconds=duration)
                    durations[twork2work[twork_id]] = duration
        return durations

    @classmethod
    def _get_invoice_values(cls, works, name):
        default = getattr(cls, 'default_%s' % name)
        durations = dict.fromkeys((w.id for w in works), default())
        method2works = defaultdict(list)
        for work in works:
            method2works[work.invoice_method].append(work)
        for method, m_works in method2works.iteritems():
            method = getattr(cls, '_get_%s_%s' % (name, method))
            # Re-browse for cache alignment
            durations.update(method(cls.browse(m_works)))
        return durations

    @classmethod
    def _get_invoiced_duration(cls, works):
        return cls._get_invoice_values(works, 'invoiced_duration')

    @classmethod
    def _get_duration_to_invoice(cls, works):
        return cls._get_invoice_values(works, 'duration_to_invoice')

    @classmethod
    def _get_invoiced_amount(cls, works):
        return cls._get_invoice_values(works, 'invoiced_amount')

    @classmethod
    @ModelView.button
    def invoice(cls, works):
        pool = Pool()
        Invoice = pool.get('account.invoice')

        invoices = []
        for work in works:
            invoice_lines = work._get_lines_to_invoice()
            if not invoice_lines:
                continue
            invoice = work._get_invoice()
            invoice.save()
            invoices.append(invoice)
            for key, lines in groupby(invoice_lines,
                    key=work._group_lines_to_invoice_key):
                lines = list(lines)
                key = dict(key)
                invoice_line = work._get_invoice_line(key, invoice, lines)
                invoice_line.invoice = invoice.id
                invoice_line.save()
                origins = {}
                for line in lines:
                    origin = line['origin']
                    origins.setdefault(origin.__class__, []).append(origin)
                for klass, records in origins.iteritems():
                    klass.save(records)  # Store first new origins
                    klass.write(records, {
                            'invoice_line': invoice_line.id,
                            })
        Invoice.update_taxes(invoices)

    def _get_invoice(self):
        "Return invoice for the work"
        pool = Pool()
        Invoice = pool.get('account.invoice')
        Journal = pool.get('account.journal')

        journals = Journal.search([
                ('type', '=', 'revenue'),
                ], limit=1)
        if journals:
            journal, = journals
        else:
            journal = None

        if not self.party:
            self.raise_user_error('missing_party', (self.rec_name,))

        return Invoice(
            company=self.company,
            type='out',
            journal=journal,
            party=self.party,
            invoice_address=self.party.address_get(type='invoice'),
            currency=self.company.currency,
            account=self.party.account_receivable,
            payment_term=self.party.customer_payment_term,
            description=self.name,
            )

    def _group_lines_to_invoice_key(self, line):
        "The key to group lines"
        return (('product', line['product']),
            ('unit_price', line['unit_price']),
            ('description', line['description']))

    def _get_invoice_line(self, key, invoice, lines):
        "Return a invoice line for the lines"
        pool = Pool()
        InvoiceLine = pool.get('account.invoice.line')
        ModelData = pool.get('ir.model.data')
        Uom = pool.get('product.uom')

        hour = Uom(ModelData.get_id('product', 'uom_hour'))
        quantity = sum(l['quantity'] for l in lines)
        product = key['product']

        invoice_line = InvoiceLine()
        invoice_line.type = 'line'
        invoice_line.quantity = Uom.compute_qty(hour, quantity,
            product.default_uom)
        invoice_line.unit = product.default_uom
        invoice_line.product = product
        invoice_line.description = key['description']
        invoice_line.account = product.account_revenue_used
        invoice_line.unit_price = Uom.compute_price(hour, key['unit_price'],
            product.default_uom)

        taxes = []
        pattern = invoice_line._get_tax_rule_pattern()
        party = invoice.party
        for tax in product.customer_taxes_used:
            if party.customer_tax_rule:
                tax_ids = party.customer_tax_rule.apply(tax, pattern)
                if tax_ids:
                    taxes.extend(tax_ids)
                continue
            taxes.append(tax.id)
        if party.customer_tax_rule:
            tax_ids = party.customer_tax_rule.apply(None, pattern)
            if tax_ids:
                taxes.extend(tax_ids)
        invoice_line.taxes = taxes
        return invoice_line

    def _get_lines_to_invoice_manual(self):
        return []

    def _get_lines_to_invoice_effort(self):
        if (not self.invoice_line
                and self.effort_hours
                and self.state == 'done'):
            if not self.product:
                self.raise_user_error('missing_product', (self.rec_name,))
            elif self.list_price is None:
                self.raise_user_error('missing_list_price', (self.rec_name,))
            return [{
                    'product': self.product,
                    'quantity': self.effort_hours,
                    'unit_price': self.list_price,
                    'origin': self,
                    'description': self.name,
                    }]
        return []

    def _get_lines_to_invoice_progress(self):
        pool = Pool()
        InvoicedProgress = pool.get('project.work.invoiced_progress')
        ModelData = pool.get('ir.model.data')
        Uom = pool.get('product.uom')

        hour = Uom(ModelData.get_id('product', 'uom_hour'))

        if self.progress is None or self.effort_duration is None:
            return []

        invoiced_progress = sum(x.effort_hours for x in self.invoiced_progress)
        quantity = self.effort_hours * self.progress - invoiced_progress
        quantity = Uom.compute_qty(hour, quantity, self.product.default_uom)
        if quantity > 0:
            if not self.product:
                self.raise_user_error('missing_product', (self.rec_name,))
            elif self.list_price is None:
                self.raise_user_error('missing_list_price', (self.rec_name,))
            invoiced_progress = InvoicedProgress(work=self,
                effort_duration=datetime.timedelta(hours=quantity))
            return [{
                    'product': self.product,
                    'quantity': quantity,
                    'unit_price': self.list_price,
                    'origin': invoiced_progress,
                    'description': self.name,
                    'description': self.name,
                    }]
        return []

    def _get_lines_to_invoice_timesheet(self):
        if self.work and self.work.timesheet_lines:
            if not self.product:
                self.raise_user_error('missing_product', (self.rec_name,))
            elif self.list_price is None:
                self.raise_user_error('missing_list_price', (self.rec_name,))
            return [{
                    'product': self.product,
                    'quantity': l.hours,
                    'unit_price': self.list_price,
                    'origin': l,
                    'description': self.name,
                    } for l in self.work.timesheet_lines
                if not l.invoice_line]
        return []

    def _test_group_invoice(self):
        return (self.company, self.party)

    def _get_lines_to_invoice(self, test=None):
        "Return lines for work and children"
        lines = []
        if test is None:
            test = self._test_group_invoice()
        lines += getattr(self, '_get_lines_to_invoice_%s' %
            self.invoice_method)()
        for children in self.children:
            if children.type == 'project':
                if test != children._test_group_invoice():
                    continue
            lines += children._get_lines_to_invoice(test=test)
        return lines
Пример #12
0
class Origin(origin_mixin(_states, _depends), 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')),
            ('date', '=', Eval('date')),
        ],
        depends=['statement', 'date', 'statement_id'])
    statement_id = fields.Function(fields.Integer("Statement ID"),
                                   'on_change_with_statement_id')
    pending_amount = fields.Function(fields.Numeric(
        "Pending Amount",
        digits=(16, Eval('_parent_statement', {}).get('currency_digits', 2))),
                                     '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)]
Пример #13
0
class Channel:
    """
    Sale Channel model
    """
    __name__ = 'sale.channel'

    # Instance
    magento_url = fields.Char("Magento Site URL",
                              states=MAGENTO_STATES,
                              depends=['source'])
    magento_api_user = fields.Char("API User",
                                   states=MAGENTO_STATES,
                                   depends=['source'])
    magento_api_key = fields.Char("API Key",
                                  states=MAGENTO_STATES,
                                  depends=['source'])
    magento_carriers = fields.One2Many("magento.instance.carrier",
                                       "channel",
                                       "Carriers / Shipping Methods",
                                       states=INVISIBLE_IF_NOT_MAGENTO,
                                       depends=['source'])
    magento_order_prefix = fields.Char(
        'Sale Order Prefix',
        help="This helps to distinguish between orders from different channels",
        states=INVISIBLE_IF_NOT_MAGENTO,
        depends=['source'])

    # website
    magento_website_id = fields.Integer('Website ID',
                                        readonly=True,
                                        states=INVISIBLE_IF_NOT_MAGENTO,
                                        depends=['source'])
    magento_website_name = fields.Char('Website Name',
                                       readonly=True,
                                       states=INVISIBLE_IF_NOT_MAGENTO,
                                       depends=['source'])
    magento_website_code = fields.Char('Website Code',
                                       readonly=True,
                                       states=INVISIBLE_IF_NOT_MAGENTO,
                                       depends=['source'])
    magento_root_category_id = fields.Integer('Root Category ID',
                                              states=INVISIBLE_IF_NOT_MAGENTO,
                                              depends=['source'])
    magento_store_name = fields.Char('Store Name',
                                     readonly=True,
                                     states=INVISIBLE_IF_NOT_MAGENTO,
                                     depends=['source'])
    magento_store_id = fields.Integer('Store ID',
                                      readonly=True,
                                      states=INVISIBLE_IF_NOT_MAGENTO,
                                      depends=['source'])

    #: Checking this will make sure that only the done shipments which have a
    #: carrier and tracking reference are exported.
    magento_export_tracking_information = fields.Boolean(
        'Export tracking information',
        help='Checking this will make sure'
        ' that only the done shipments which have a carrier and tracking '
        'reference are exported. This will update carrier and tracking '
        'reference on magento for the exported shipments as well.',
        states=INVISIBLE_IF_NOT_MAGENTO,
        depends=['source'])
    magento_taxes = fields.One2Many("sale.channel.magento.tax",
                                    "channel",
                                    "Taxes",
                                    states=INVISIBLE_IF_NOT_MAGENTO,
                                    depends=['source'])
    magento_price_tiers = fields.One2Many('sale.channel.magento.price_tier',
                                          'channel',
                                          'Default Price Tiers',
                                          states=INVISIBLE_IF_NOT_MAGENTO,
                                          depends=['source'])
    product_listings = fields.One2Many(
        'product.product.channel_listing',
        'channel',
        'Product Listings',
    )
    magento_payment_gateways = fields.One2Many(
        'magento.instance.payment_gateway',
        'channel',
        'Payments',
    )

    @classmethod
    def __setup__(cls):
        """
        Setup the class before adding to pool
        """
        super(Channel, cls).__setup__()
        cls._sql_constraints += [
            ('unique_magento_channel',
             'UNIQUE(magento_url, magento_website_id, magento_store_id)',
             'This store is already added')
        ]
        cls._error_messages.update({
            "connection_error":
            "Incorrect API Settings! \n"
            "Please check and correct the API settings on channel.",
            "multiple_channels":
            'Selected operation can be done only for one'
            ' channel at a time',
            'invalid_magento_channel':
            'Current channel does not belongs to Magento !'
        })
        cls._buttons.update({
            'import_magento_carriers': {
                'invisible': Eval('source') != 'magento'
            },
            'configure_magento_connection': {
                'invisible': Eval('source') != 'magento'
            }
        })

    def validate_magento_channel(self):
        """
        Make sure channel source is magento
        """
        if self.source != 'magento':
            self.raise_user_error('invalid_magento_channel')

    @classmethod
    def get_source(cls):
        """
        Get the source
        """
        res = super(Channel, cls).get_source()
        res.append(('magento', 'Magento'))
        return res

    @staticmethod
    def default_magento_order_prefix():
        """
        Sets default value for magento order prefix
        """
        return 'mag_'

    @staticmethod
    def default_magento_root_category_id():
        """
        Sets default root category id. Is set to 1, because the default
        root category is 1
        """
        return 1

    def get_taxes(self, rate):
        "Return list of tax records with the given rate"
        for mag_tax in self.magento_taxes:
            if mag_tax.tax_percent == rate:
                return list(mag_tax.taxes)
        return []

    def import_order_states(self):
        """
        Import order states for magento channel

        Downstream implementation for channel.import_order_states
        """
        if self.source != 'magento':
            return super(Channel, self).import_order_states()

        with Transaction().set_context({'current_channel': self.id}):
            # Import order states
            with OrderConfig(self.magento_url, self.magento_api_user,
                             self.magento_api_key) as order_config_api:
                order_states_data = order_config_api.get_states()
                for code, name in order_states_data.iteritems():
                    self.create_order_state(code, name)

    @classmethod
    @ModelView.button_action('magento.wizard_configure_magento')
    def configure_magento_connection(cls, channels):
        """
        Configure magento connection for current channel

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

    def test_magento_connection(self):
        """
        Test magento connection and display appropriate message to user
        :param channels: Active record list of magento channels
        """
        # Make sure channel belongs to magento
        self.validate_magento_channel()

        try:
            with magento.API(self.magento_url, self.magento_api_user,
                             self.magento_api_key):
                return
        except (xmlrpclib.Fault, IOError, xmlrpclib.ProtocolError,
                socket.timeout):
            self.raise_user_error("connection_error")

    @classmethod
    @ModelView.button_action('magento.wizard_import_magento_carriers')
    def import_magento_carriers(cls, channels):
        """
        Import carriers/shipping methods from magento for channels

        :param channels: Active record list of magento channels
        """
        InstanceCarrier = Pool().get('magento.instance.carrier')

        for channel in channels:
            channel.validate_magento_channel()
            with Transaction().set_context({'current_channel': channel.id}):
                with OrderConfig(channel.magento_url, channel.magento_api_user,
                                 channel.magento_api_key) as order_config_api:
                    mag_carriers = order_config_api.get_shipping_methods()

                InstanceCarrier.create_all_using_magento_data(mag_carriers)

    @classmethod
    def get_current_magento_channel(cls):
        """Helper method to get the current magento_channel.
        """
        channel = cls.get_current_channel()

        # Make sure channel belongs to magento
        channel.validate_magento_channel()

        return channel

    def import_products(self):
        """
        Import products for this magento channel

        Downstream implementation for channel.import_products
        """
        if self.source != 'magento':
            return super(Channel, self).import_products()

        self.import_category_tree()

        with Transaction().set_context({'current_channel': self.id}):
            with magento.Product(self.magento_url, self.magento_api_user,
                                 self.magento_api_key) as product_api:
                # TODO: Implement pagination and import each product as async
                # task
                magento_products = product_api.list()

                products = []
                for magento_product in magento_products:
                    products.append(self.import_product(
                        magento_product['sku']))

        return products

    def import_product(self, sku):
        """
        Import specific product for this magento channel

        Downstream implementation for channel.import_product
        """
        Product = Pool().get('product.product')
        Listing = Pool().get('product.product.channel_listing')

        if self.source != 'magento':
            return super(Channel, self).import_product(sku)

        # Sanitize SKU
        sku = sku.strip()

        products = Product.search([
            ('code', '=', sku),
        ])
        listings = Listing.search([('product.code', '=', sku),
                                   ('channel', '=', self)])

        if not products or not listings:
            # Either way we need the product data from magento. Make that
            # dreaded API call.
            with magento.Product(self.magento_url, self.magento_api_user,
                                 self.magento_api_key) as product_api:
                product_data = product_api.info(sku, identifierType="SKU")

                # XXX: sanitize product_data, sometimes product sku may
                # contain trailing spaces
                product_data['sku'] = product_data['sku'].strip()

            # Create a product since there is no match for an existing
            # product with the SKU.
            if not products:
                product = Product.create_from(self, product_data)
            else:
                product, = products

            if not listings:
                Listing.create_from(self, product_data)
        else:
            product = products[0]

        return product

    def import_category_tree(self):
        """
        Imports the category tree and creates categories in a hierarchy same as
        that on Magento

        :param website: Active record of website
        """
        Category = Pool().get('product.category')

        self.validate_magento_channel()

        with Transaction().set_context({'current_channel': self.id}):
            with magento.Category(self.magento_url, self.magento_api_user,
                                  self.magento_api_key) as category_api:
                category_tree = category_api.tree(
                    self.magento_root_category_id)
                Category.create_tree_using_magento_data(category_tree)

    def import_orders(self):
        """
        Downstream implementation of channel.import_orders

        :return: List of active record of sale imported
        """
        if self.source != 'magento':
            return super(Channel, self).import_orders()

        new_sales = []
        with Transaction().set_context({'current_channel': self.id}):
            order_states = self.get_order_states_to_import()
            order_states_to_import_in = map(lambda state: state.code,
                                            order_states)

            with magento.Order(self.magento_url, self.magento_api_user,
                               self.magento_api_key) as order_api:
                # Filter orders with date and store_id using list()
                # then get info of each order using info()
                # and call find_or_create_using_magento_data on sale
                filter = {
                    'store_id': {
                        '=': self.magento_store_id
                    },
                    'state': {
                        'in': order_states_to_import_in
                    },
                }
                if self.last_order_import_time:
                    last_order_import_time = \
                        self.last_order_import_time.replace(
                            microsecond=0
                        )
                    filter.update({
                        'updated_at': {
                            'gteq': last_order_import_time.isoformat(' ')
                        },
                    })
                self.write([self],
                           {'last_order_import_time': datetime.utcnow()})
                page = 1
                has_next = True
                orders_summaries = []
                while has_next:
                    # XXX: Pagination is only available in
                    # magento extension >= 1.6.1
                    api_res = order_api.search(filters=filter,
                                               limit=3000,
                                               page=page)
                    has_next = api_res['hasNext']
                    page += 1
                    orders_summaries.extend(api_res['items'])

                for order_summary in orders_summaries:
                    new_sales.append(self.import_order(order_summary))
        return new_sales

    def import_order(self, order_info):
        "Downstream implementation to import sale order from magento"
        if self.source != 'magento':
            return super(Channel, self).import_order(order_info)

        Sale = Pool().get('sale.sale')

        sale = Sale.find_using_magento_data(order_info)
        if sale:
            return sale

        with Transaction().set_context({'current_channel': self.id}):
            with magento.Order(self.magento_url, self.magento_api_user,
                               self.magento_api_key) as order_api:
                order_data = order_api.info(order_info['increment_id'])
                return Sale.create_using_magento_data(order_data)

    @classmethod
    def export_order_status_to_magento_using_cron(cls):
        """
        Export sales orders status to magento using cron

        :param store_views: List of active record of store view
        """
        channels = cls.search([('source', '=', 'magento')])

        for channel in channels:
            channel.export_order_status_to_magento()

    def export_order_status_to_magento(self):
        """
        Export sale orders to magento for the current store view.
        If last export time is defined, export only those orders which are
        updated after last export time.

        :return: List of active records of sales exported
        """
        Sale = Pool().get('sale.sale')

        self.validate_magento_channel()

        exported_sales = []
        domain = [('channel', '=', self.id)]

        if self.last_order_export_time:
            domain = [('write_date', '>=', self.last_order_export_time)]

        sales = Sale.search(domain)

        self.last_order_export_time = datetime.utcnow()
        self.save()

        for sale in sales:
            exported_sales.append(sale.export_order_status_to_magento())

        return exported_sales

    @classmethod
    def export_shipment_status_to_magento_using_cron(cls):
        """
        Export Shipment status for shipments using cron
        """
        channels = cls.search([('source', '=', 'magento')])

        for channel in channels:
            channel.export_shipment_status_to_magento()

    def export_shipment_status_to_magento(self):
        """
        Exports shipment status for shipments to magento, if they are shipped

        :return: List of active record of shipment
        """
        Shipment = Pool().get('stock.shipment.out')
        Sale = Pool().get('sale.sale')
        SaleLine = Pool().get('sale.line')

        self.validate_magento_channel()

        sale_domain = [
            ('channel', '=', self.id),
            ('shipment_state', '=', 'sent'),
            ('magento_id', '!=', None),
            ('shipments', '!=', None),
        ]

        if self.last_shipment_export_time:
            sale_domain.append(
                ('write_date', '>=', self.last_shipment_export_time))

        sales = Sale.search(sale_domain)

        self.last_shipment_export_time = datetime.utcnow()
        self.save()

        updated_sales = set([])
        for sale in sales:
            # Get the increment id from the sale reference
            increment_id = sale.reference[len(self.magento_order_prefix
                                              ):len(sale.reference)]

            for shipment in sale.shipments:
                try:
                    # Some checks to make sure that only valid shipments are
                    # being exported
                    if shipment.is_tracking_exported_to_magento or \
                            shipment.state != 'done' or \
                            shipment.magento_increment_id:
                        continue
                    updated_sales.add(sale)
                    with magento.Shipment(
                            self.magento_url, self.magento_api_user,
                            self.magento_api_key) as shipment_api:
                        item_qty_map = {}
                        for move in shipment.outgoing_moves:
                            if isinstance(move.origin, SaleLine) \
                                    and move.origin.magento_id:
                                # This is done because there can be multiple
                                # lines with the same product and they need
                                # to be send as a sum of quanitities
                                item_qty_map.setdefault(
                                    str(move.origin.magento_id), 0)
                                item_qty_map[str(move.origin.magento_id)] += \
                                    move.quantity
                        shipment_increment_id = shipment_api.create(
                            order_increment_id=increment_id,
                            items_qty=item_qty_map)
                        Shipment.write(
                            list(sale.shipments), {
                                'magento_increment_id': shipment_increment_id,
                            })

                        if self.magento_export_tracking_information and (
                                hasattr(shipment, 'tracking_number')
                                and hasattr(shipment, 'carrier') and
                                shipment.tracking_number and shipment.carrier):
                            with Transaction().set_context(
                                    current_channel=self.id):
                                shipment.export_tracking_info_to_magento()
                except xmlrpclib.Fault, fault:
                    if fault.faultCode == 102:
                        # A shipment already exists for this order,
                        # we cannot do anything about it.
                        # Maybe it was already exported earlier or was created
                        # separately on magento
                        # Hence, just continue
                        continue

        return updated_sales
Пример #14
0
class Template(ModelSQL, ModelView):
    "Product Template"
    _name = "product.template"
    _description = __doc__

    name = fields.Char('Name',
                       size=None,
                       required=True,
                       translate=True,
                       select=1,
                       states=STATES)
    type = fields.Selection(_TYPE_PRODUCT,
                            'Type',
                            required=True,
                            states=STATES)
    category = fields.Many2One('product.category',
                               'Category',
                               required=True,
                               states=STATES)
    group = fields.Many2Many('product.grouping',
                             'product',
                             'group',
                             'Group',
                             states=STATES)
    default_uom = fields.Many2One('product.uom',
                                  'Default UOM',
                                  required=True,
                                  states=STATES)
    active = fields.Boolean('Active', select=1)
    products = fields.One2Many('product.product',
                               'template',
                               'Products',
                               states=STATES)
    analogue = fields.Many2Many('ekd.product.analogue',
                                'original',
                                'product',
                                'Product Analogue',
                                select=1)
    product_template = fields.Many2One(
        'ekd.product.template',
        'Product Template',
        on_change=['product_template', 'properties', 'description'])
    properties = fields.One2Many('ekd.product.property',
                                 'product',
                                 'Property',
                                 select=2)
    as_material = fields.Boolean('As Material', select=1)
    as_fixed = fields.Boolean('As Fixed Assets', select=1)
    as_intangible = fields.Boolean('As Intangible Assets', select=1)
    as_goods = fields.Boolean('As Goods', select=1)

    def default_active(self):
        return True

    def default_type(self):
        return 'stockable'

    def default_cost_price_method(self):
        return 'fixed'

    def get_price_uom(self, ids, name):
        product_uom_obj = self.pool.get('product.uom')
        res = {}
        field = name[:-4]
        context = Transaction().context
        if context.get('uom'):
            to_uom = self.pool.get('product.uom').browse(
                Transaction().context['uom'])
            for product in self.browse(ids):
                res[product.id] = product_uom_obj.compute_price(
                    product.default_uom, product[field], to_uom)
        else:
            for product in self.browse(ids):
                res[product.id] = product[field]
        return res

    def copy(self, ids, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default['products'] = False
        return super(Template, self).copy(ids, default=default)
Пример #15
0
class PatientRounding(Workflow, ModelSQL, ModelView):
    'Patient Ambulatory Care'
    __name__ = 'gnuhealth.patient.rounding'

    hospitalization_location = fields.Many2One(
        'stock.location',
        'Hospitalization Location',
        domain=[('type', '=', 'storage')],
        states={
            'required':
            If(Or(Bool(Eval('medicaments')), Bool(Eval('medical_supplies'))),
               True, False),
            'readonly':
            Eval('state') == 'done',
        },
        depends=_DEPENDS)
    medicaments = fields.One2Many('gnuhealth.patient.rounding.medicament',
                                  'name',
                                  'Medicaments',
                                  states=_STATES,
                                  depends=_DEPENDS)
    medical_supplies = fields.One2Many(
        'gnuhealth.patient.rounding.medical_supply',
        'name',
        'Medical Supplies',
        states=_STATES,
        depends=_DEPENDS)
    moves = fields.One2Many('stock.move',
                            'origin',
                            'Stock Moves',
                            readonly=True)

    @classmethod
    def __setup__(cls):
        super(PatientRounding, cls).__setup__()
        cls._transitions |= set((('draft', 'done'), ))
        cls._buttons.update(
            {'done': {
                'invisible': ~Eval('state').in_(['draft']),
            }})

    @classmethod
    def copy(cls, roundings, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default['moves'] = None
        return super(PatientRounding, cls).copy(roundings, default=default)

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def done(cls, roundings):
        pool = Pool()
        Patient = pool.get('gnuhealth.patient')
        HealthProfessional = pool.get('gnuhealth.healthprofessional')

        signing_hp = HealthProfessional.get_health_professional()

        lines_to_ship = {}
        medicaments_to_ship = []
        supplies_to_ship = []

        for rounding in roundings:
            patient = Patient(rounding.name.patient.id)

            for medicament in rounding.medicaments:
                medicaments_to_ship.append(medicament)

            for medical_supply in rounding.medical_supplies:
                supplies_to_ship.append(medical_supply)

        lines_to_ship['medicaments'] = medicaments_to_ship
        lines_to_ship['supplies'] = supplies_to_ship

        cls.create_stock_moves(roundings, lines_to_ship)

        cls.write(roundings, {
            'signed_by': signing_hp,
            'evaluation_end': datetime.now()
        })

    @classmethod
    def create_stock_moves(cls, roundings, lines):
        pool = Pool()
        Move = pool.get('stock.move')
        Date = pool.get('ir.date')
        moves = []
        for rounding in roundings:
            for medicament in lines['medicaments']:
                move_info = {}
                move_info['origin'] = str(rounding)
                move_info['product'] = medicament.medicament.name.id
                move_info['uom'] = medicament.medicament.name.default_uom.id
                move_info['quantity'] = medicament.quantity
                move_info['from_location'] = \
                    rounding.hospitalization_location.id
                move_info['to_location'] = \
                rounding.name.patient.name.customer_location.id
                move_info['unit_price'] = medicament.medicament.name.list_price
                if medicament.lot:
                    if  medicament.lot.expiration_date \
                    and medicament.lot.expiration_date < Date.today():
                        raise UserError('Expired medicaments')
                    move_info['lot'] = medicament.lot.id
                moves.append(move_info)
            for medical_supply in lines['supplies']:
                move_info = {}
                move_info['origin'] = str(rounding)
                move_info['product'] = medical_supply.product.id
                move_info['uom'] = medical_supply.product.default_uom.id
                move_info['quantity'] = medical_supply.quantity
                move_info['from_location'] = \
                    rounding.hospitalization_location.id
                move_info['to_location'] = \
                rounding.name.patient.name.customer_location.id
                move_info['unit_price'] = medical_supply.product.list_price
                if medical_supply.lot:
                    if  medical_supply.lot.expiration_date \
                    and medical_supply.lot.expiration_date < Date.today():
                        raise UserError('Expired supplies')
                    move_info['lot'] = medical_supply.lot.id
                moves.append(move_info)
        new_moves = Move.create(moves)
        Move.write(new_moves, {
            'state': 'done',
            'effective_date': Date.today(),
        })

        return True
Пример #16
0
class IncomeTaxDeduction(ModelSQL, ModelView):
    """ Income Tax projections and real outcomes """

    __name__ = 'income_tax.deduction'

    salary_code = fields.Char('Salary Code')
    employee = fields.Many2One('company.employee', 'Employee')
    designation = fields.Many2One('employee.designation', 'Designation')
    department = fields.Many2One('company.department', 'Department')
    start_date = fields.Date('Start Date')
    end_date = fields.Date('End Date')
    fiscal_year = fields.Many2One('account.fiscalyear', 'Fiscal Year')
    annual_salary_ytd = fields.Float(
        'Annual Salary (YTD)'
    )
    annual_salary_projected = fields.Float(
        'Annual Salary (Projected)'
    )
    income_from_other_source = fields.Float(
        'Income from other Sources'
    )
    annual_taxable_income_ytd = fields.Float(
        'Annual Taxable Income (YTD)',
        help="Annual Taxable Income on which "
        "we calculate the income tax."
    )
    annual_taxable_income_projected = fields.Float(
        'Annual Taxable Income (Projected)',
        help="Projected Annual Taxable Income on "
        "which we calculate the income tax."
    )
    income_tax_projected = fields.Float(
        'Projected Income Tax',
        help="This is subjected to variations based on "
        "the Income Tax Declarations, change in Salary "
        "or any Govt. Rules or policies")
    income_tax_ytd = fields.Float(
        'Income Tax (Year To Date)',
        help="This is the actual TDS deducted in "
        "the current financial year.")
    tds_lines = fields.One2Many(
        'income_tax.taxable_amount_lines',
        'taxable_amount_projections',
        'Projected Income Taxable Amount Lines'
    )

    # For calculation purpose only - Start
    projected_tds_lines = fields.One2Many(
        'income_tax.taxable_amount_lines',
        'taxable_amount_projections',
        'Projected Income Taxable Amount Lines',
        domain=[('state', '=', 'projected')]
    )
    deducted_tds_lines = fields.One2Many(
        'income_tax.taxable_amount_lines',
        'taxable_amount_projections',
        'Deducted Income Taxable Amount Lines',
        domain=[('state', '=', 'deducted')]
    )

    @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):
        if self.employee:
            self.salary_code = self.employee.salary_code
            self.designation = self.employee.designation
            self.department = self.employee.department

    @staticmethod
    def default_start_date():
        '''
        returns start_date as this year's current
        fiscal year's start_date value.
        '''
        pool = Pool()
        fiscal = pool.get('account.fiscalyear')
        company = Transaction().context.get('company')
        current_fiscal_year = fiscal.find(company)
        return fiscal(current_fiscal_year).start_date \
            if current_fiscal_year else None

    # @staticmethod
    # def default_fiscal_year():
    #     '''
    #     returns start_date as this year's current
    #     fiscal year's start_date value.
    #     '''
    #     current_fiscal_year = None
    #     pool = Pool()
    #     fiscal = pool.get('account.fiscalyear')
    #     company = Transaction().context.get('company')
    #     if fiscal.find(company):
    #         current_fiscal_year = fiscal.find(company)
    #     return current_fiscal_year

    @staticmethod
    def default_end_date():
        '''
        returns end_date as this year's current
        fiscal year's end_date value.
        '''
        pool = Pool()
        fiscal = pool.get('account.fiscalyear')
        company = Transaction().context.get('company')
        current_fiscal_year = fiscal.find(company)
        return fiscal(current_fiscal_year).end_date \
            if current_fiscal_year else None

    def get_tax_exemption(self, month, year):
        '''Get the tax exemption from the current
        year's investment declaration'''
        pool = Pool()
        tax_exemption = 0
        current_inv_declaration_1 = None
        fiscal = pool.get('account.fiscalyear')
        inv_declaration = pool.get('investment.declaration')
        company = Transaction().context.get('company')
        current_fiscal_year = fiscal.find(company)
        current_date = datetime.date(year, int(month), 1)
        current_inv_declaration = inv_declaration.search([
            ('employee', '=', self.employee),
            ('fiscal_year', '=', current_fiscal_year)
        ], order=[('write_date', 'DESC')])
        for declaration in current_inv_declaration:
            date = declaration.write_date.date()
            if date <= current_date:
                current_inv_declaration_1 = declaration
                break
        if current_inv_declaration_1:
            tax_exemption = current_inv_declaration_1.net_tax_exempted
        return tax_exemption

    def get_tds_line(self, month, year):
        '''Returns the dictionary for TDS Lines'''
        vals = {}
        for line in self.tds_lines:
            if line.month == month and line.year == year:
                vals = {
                    'tds': line.amount,
                    'state': line.state,
                }
        return vals

    def calculate_monthly_tds(self):
        '''
        get total taxed amount till date and
        add projected taxed amount plus
        the investment declaration, if any
        based on the tax slab
        for the remaining months on monthly basis
        '''
        pool = Pool()
        TaxSlab = pool.get('income_tax.rule')
        Payslip = pool.get('hr.payslip')
        PayslipLine = Pool().get('hr.payslip.line')
        fiscal = pool.get('account.fiscalyear')
        company = Transaction().context.get('company')
        current_fiscal_year = fiscal.find(company)
        # Get payslips of current fiscal year
        payslips = Payslip.search([
            ('employee', '=', self.employee),
            ('fiscal_year', '=', current_fiscal_year)
        ], order=[('year', 'ASC')])
        annual_tax = TaxSlab.get_annual_income_tax(
            self.employee,
            self.annual_taxable_income_projected
        )
        current_month = datetime.date.today().month
        current_year = datetime.date.today().year
        tax_exemption = self.get_tax_exemption(
            str(current_month), current_year
        )
        # Saving Projected Income Tax for current fiscal year
        if annual_tax:
            self.income_tax_projected = annual_tax
        else:
            self.raise_user_error('No Income Tax Rule defined')
        if not self.tds_lines:
            # If there are no tds lines, then create the new sheet
            TDSLines = pool.get('income_tax.taxable_amount_lines')
            if payslips:
                for payslip in payslips:
                    if int(payslip.month) == current_month:
                        break
                    payslip_lines = PayslipLine.search([
                        ('payslip', '=', payslip),
                        ('salary_rule.code', '=', 'TDS')
                    ])
                    payslip_tds = payslip_lines[0] \
                        if payslip_lines else None
                    TDSLines.create([
                        {
                            'month': payslip.month,
                            'year': payslip.year,
                            'amount': payslip_tds.amount
                            if payslip_tds else 0,
                            'state': 'deducted',
                            'taxable_amount_projections': self
                        }
                    ])
            if tax_exemption:
                monthly_tds = (annual_tax / 12) - (tax_exemption / 12)
            else:
                monthly_tds = (annual_tax / 12)
            if current_month >= 4:
                for month in range(current_month, 13):
                    TDSLines.create([
                        {
                            'month': str(month),
                            'year': datetime.date.today().year,
                            'amount': monthly_tds,
                            'state': 'projected',
                            'taxable_amount_projections': self
                        }
                    ])
            for month in range(current_month if current_month < 4 else 1, 4):
                TDSLines.create(
                    [
                        {
                            'month': str(month),
                            'year': datetime.date.today().year + 1,
                            'amount': monthly_tds,
                            'state': 'projected',
                            'taxable_amount_projections': self
                        }
                    ]
                )
        else:
            payslips = Payslip.search([
                ('employee', '=', self.employee),
                ('fiscal_year', '=', self.fiscal_year),
            ], order=[('year', 'ASC')])
            for payslip in payslips:
                tds_month = 0
                exemption = 0
                if int(payslip.month) == current_month:
                    break
                payslip_lines = PayslipLine.search([
                    ('payslip', '=', payslip),
                    ('salary_rule.code', '=', 'TDS')
                ])
                month = int(payslip.month)
                payslip_tds = payslip_lines[0] \
                    if payslip_lines else None
                if payslip_tds:
                    tds_month = payslip_tds.amount
                    if tds_month == 0:
                        ded = 0
                        for line in self.tds_lines:
                            if line.state == 'deducted':
                                ded += line.amount
                            else:
                                break
                        remaining_tax = annual_tax - ded
                        exemption = self.get_tax_exemption(
                            payslip.month, payslip.year
                        )
                        remaining_months = 12 \
                            if month == 4 else self.get_remaining_months(month)
                        tds_month = remaining_tax / remaining_months \
                            - exemption / remaining_months
                        payslip_tds.amount = tds_month
                        payslip_tds.save()
                    for line in self.tds_lines:
                        if line.month == payslip.month \
                                and line.year == payslip.year:
                            line.amount = tds_month
                            if line.state == 'projected':
                                line.state = 'deducted'
                            line.save()
                            break
            deducted = 0
            for line in self.tds_lines:
                if line.state == 'deducted':
                    deducted += line.amount
                else:
                    break
            remaining_tax = annual_tax - deducted
            remaining_months = self.get_remaining_months(current_month)
            remaining_monthly_tds = remaining_tax / remaining_months \
                - tax_exemption/remaining_months
            for pline in self.tds_lines:
                if pline.state == 'projected':
                    pline.amount = remaining_monthly_tds
                    pline.save()
        # TODO: review the method for calculation button calls twice

    def calculate_income_tax_ytd(self):
        '''
        TODO: review the docstring
        '''
        pool = Pool()
        Payslip = pool.get('hr.payslip')
        PayslipLine = pool.get('hr.payslip.line')
        payslips = Payslip.search([
            ('employee', '=', self.employee),
            ('fiscal_year', '=', self.fiscal_year),
        ])
        annual_tax_ytd = 0
        for payslip in payslips:
            payslip_tds = PayslipLine.search([
                ('payslip', '=', payslip),
                ('salary_rule.code', '=', 'TDS'),
            ])
            tds = payslip_tds[0].amount if payslip_tds else 0
            annual_tax_ytd += tds
        # Saving Income Tax YTD for current fiscal year
        self.income_tax_ytd = annual_tax_ytd

    def get_remaining_months(self, month):
        '''
        calculate the remaining number of months
        in the current financial year
        '''
        current_month = month
        if current_month <= 12 and current_month > 3:
            remaining_months = (12-current_month)+4
            # 3 months in the next year and 1 to include the current month
            return remaining_months
        elif current_month <= 3:
            remaining_months = (3-current_month)+1
            # 1 to include the current month
            return remaining_months

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

    def calculate_income_from_other_sources(self):
        '''Get Income from other sources from Investment Decalaration'''
        '''
            Algo -
            Go to employee > current year investment_decalartion
            and get the income from other sources
        '''
        employee_investment = []
        pool = Pool()
        Investment = pool.get('investment.declaration')
        investments = Investment.search(
            [
                ('employee', '=', self.employee),
            ], order=[('write_date', 'DESC')]
        )
        if investments:
            employee_investment = investments[0]
            self.income_from_other_source = \
                employee_investment.total_income_declared
        else:
            self.income_from_other_source = 0

    def calculate_taxable_salary_ytd(self):
        '''Get taxable salary from the payslips in current fiscal year'''
        '''
            Algo -
            Go to Employee > Payslips.
            Search for current year's payslips.
            You will find the taxable income.
        '''
        pool = Pool()
        Payslip = pool.get('hr.payslip')
        fiscal = pool.get('account.fiscalyear')
        current_month = datetime.date.today().month
        company = Transaction().context.get('company')
        current_fiscal_year = fiscal.find(company)
        vals = [
            ('employee', '=', self.employee),
            ('fiscal_year', '=', current_fiscal_year),
        ]
        payslips = Payslip.search(
            vals, order=[('year', 'ASC')]
        )
        res_salary = 0
        for payslip in payslips:
            if int(payslip.month) == current_month:
                break
            res_salary += payslip.get_taxable_salary_amount()
        self.annual_salary_ytd = res_salary
        self.save()

    def calculate_taxable_salary_projected(self):
        '''
            Algo -
            1. Get the taxable salary from last month's salary slip.
            2. Multiply last salary in remaining months
            3. Add the YTD salary
        '''
        pool = Pool()
        vals = []
        current_month = datetime.date.today().month
        Payslip = pool.get('hr.payslip')
        fiscal = pool.get('account.fiscalyear')
        company = Transaction().context.get('company')
        current_fiscal_year = fiscal.find(company)
        if current_month == 4:
            current_year = datetime.date.today().year
            current_date = datetime.date(current_year, 4, 1)
            date_prev_year = datetime.date(current_year-1, 4, 1)
            prev_fiscal_year = fiscal.search([
                ('end_date', '<', current_date),
                ('end_date', '>=', date_prev_year),
            ])
            vals = [
                ('employee', '=', self.employee),
                ('month', '=', str(current_month-1)),
                ('fiscal_year', '=', prev_fiscal_year),
            ]
        else:
            vals = [
                ('employee', '=', self.employee),
                ('month', '=', str(current_month-1)),
                ('fiscal_year', '=', current_fiscal_year),
            ]
        payslip_prev_month = Payslip.search(vals)
        global current_payslip
        res = 0
        taxable_salary_prev_month = 0
        remaining_months = self.get_remaining_months(current_month)
        if payslip_prev_month:
            current_payslip = payslip_prev_month[0]
            taxable_salary_prev_month =  \
                current_payslip.get_taxable_salary_amount()
        res = taxable_salary_prev_month * remaining_months
        payslips = Payslip.search([
            ('employee', '=', self.employee),
            ('fiscal_year', '=', current_fiscal_year),
        ], order=[('year', 'ASC')])
        res_salary = 0
        for payslip in payslips:
            if int(payslip.month) == current_month:
                break
            res_salary += payslip.get_taxable_salary_amount()
        res_salary_projected = res_salary + res
        # TODO: Figure out projected salary for month of April
        self.annual_salary_projected = res_salary_projected

    def calculate_annual_taxable_income_ytd(self):
        '''Calculate annual taxable using the following formula:

        taxable salary from payslips in current fiscal year +
        income from other source
        '''

        self.annual_taxable_income_ytd = (
            self.annual_salary_ytd + self.income_from_other_source)

    def calculate_annual_taxable_income_projected(self):
        '''Calculate the projected annual taxable income:
        '''
        self.annual_taxable_income_projected = (
            self.annual_salary_projected + self.income_from_other_source)

    @classmethod
    def calculate_income_tax_sheet(cls, records):
        '''Calculate Entire Income Tax Sheet'''
        for record in records:
            record.calculate_income_from_other_sources()
            record.calculate_taxable_salary_ytd()
            record.calculate_taxable_salary_projected()
            record.calculate_annual_taxable_income_ytd()
            record.calculate_annual_taxable_income_projected()
            record.calculate_income_tax_ytd()
            record.calculate_monthly_tds()
            record.save()
Пример #17
0
class Contract(ModelWorkflow, ModelSQL, ModelView):
    """Contract Agreement"""
    _name = 'contract.contract'
    _description = __doc__

    company = fields.Many2One('company.company', 'Company', required=True,
                         states=STATES, select=1, domain=[
                             ('id', If(In('company', Eval('context', {})), '=', '!='),
                              Get(Eval('context', {}), 'company', 0)),
                         ])
    journal = fields.Many2One('account.journal', 'Journal', required=True, 
                              states=STATES, domain=[('centralised', '=', False)])
    name = fields.Char('Name', required=True, translate=True)
    description = fields.Char('Description', size=None, translate=True)
    reference = fields.Char('Reference', size=None, translate=True,
                            states=STATES, help='Use for purchase orders')
    party = fields.Many2One('party.party', 'Party', required=True, 
                            states=STATES, on_change=['party'])
    product = fields.Many2One('product.product', 'Product', required=True,
                              states=STATES)
    list_price = fields.Numeric('List Price', states=STATES,
                                digits=(16, 4),
                               help='''Fixed-price override. Leave at 0.0000 for no override. Use discount at 100% to set actual price to 0.0''')
    discount = fields.Numeric('Discount (%)', states=STATES,
                              digits=(4,2),
                             help='Discount percentage on the list_price')
    quantity = fields.Numeric('Quantity', digits=(16,2), states=STATES)
    payment_term = fields.Many2One('account.invoice.payment_term',
                                   'Payment Term', required=True,
                                   states=STATES)
    state = fields.Selection([
        ('draft','Draft'),
        ('active','Active'),
        ('hold', 'Hold'),
        ('canceled','Canceled'),
    ], 'State', readonly=True)
    interval = fields.Selection([
        ('day','Day'),
        ('week','Week'),
        ('month','Month'),
        ('year','Year'),
    ], 'Interval', required=True, states=STATES)
    interval_quant = fields.Integer('Interval count', states=STATES)
    next_invoice_date = fields.Date('Next Invoice', states=STATES)
    opt_invoice_date = fields.Date('Temporary Next Invoice', readonly=True)
    start_date = fields.Date('Since', states=STATES)
    stop_date = fields.Date('Until')
    lines = fields.One2Many('account.invoice.line', 'contract', 'Invoice Lines',
                           readonly=True, domain=[('contract','=',Eval('id'))])


    def __init__(self):
        super(Contract, self).__init__()
        self._rpc.update({
            'create_next_invoice': True,
            'create_invoice_batch': True,
            'cancel_with_credit': True,
        })

    def default_state(self):
        return 'draft'

    def default_interval(self):
        return 'month'

    def default_quantity(self):
        return Decimal('1.0')

    def default_company(self):
        return Transaction().context.get('company') or False

    def default_start_date(self):
        return datetime.date.fromtimestamp(time.time())

    def default_interval_quant(self):
        return Decimal("1.0")

    def default_payment_term(self):
        config_obj = self.pool.get('contract.configuration')
        config = config_obj.browse(1)
        if config.payment_term:
            return config.payment_term.id

        company_id = self.default_company()
        company_obj = self.pool.get('company.company')
        company = company_obj.browse(company_id)
        if company and company.payment_term:
            return company.payment_term.id
        return False

    def default_journal(self):
        journal_obj = self.pool.get('account.journal')
        journal_ids = journal_obj.search([('type','=','revenue')], limit=1)
        if journal_ids:
            return journal_ids[0]
        return False

    def on_change_party(self, vals):
        party_obj = self.pool.get('party.party')
        address_obj = self.pool.get('party.address')
        payment_term_obj = self.pool.get('account.invoice.payment_term')
        res = {
            'invoice_address': False,
        }
        if vals.get('party'):
            party = party_obj.browse(vals['party'])
            payment_term = party.payment_term or False
            discount = party.discount or Decimal("0.0")
            if payment_term:
                res['payment_term'] = payment_term.id
                res['payment_term.rec_name'] = payment_term_obj.browse(
                    res['payment_term']).rec_name
            res['discount'] = discount
        return res

    def write(self, ids, vals):
        if 'state' in vals:
            self.workflow_trigger_trigger(ids)

        return super(Contract, self).write(ids, vals)

    def _invoice_init(self, contract, invoice_date):
        invoice_obj = self.pool.get('account.invoice')
        invoice_address = contract.party.address_get(contract.party.id, type='invoice')
        config_obj = self.pool.get('contract.configuration')
        config = config_obj.browse(1)
        description = config.description

        invoice = invoice_obj.create(dict(
            company=contract.company.id,
            type='out_invoice',
            description=description,
            state='draft',
            currency=contract.company.currency.id,
            journal=contract.journal.id,
            account=contract.party.account_receivable.id or contract.company.account_receivable.id,
            payment_term=contract.party.payment_term.id or contract.payment_term.id,
            party=contract.party.id,
            invoice_address=invoice_address,
            invoice_date=invoice_date,
        ))
        return invoice_obj.browse([invoice])[0]

    def _contract_unit_price(self, contract):
        unit_price = contract.product.list_price
        if contract.list_price:
            unit_price = contract.list_price
        elif contract.discount:
            unit_price = contract.product.list_price - (contract.product.list_price * contract.discount / 100)
        return unit_price


    def _invoice_append(self, invoice, contract, period):
        (last_date, next_date, quant) = period
        line_obj = self.pool.get('account.invoice.line')

        quantity = Decimal("%f" % quant)
        unit_price = self._contract_unit_price(contract)
        if not unit_price:
            # skip this contract
            log.info("skip contract without unit_price: %s contract.product.list_price: %s contract.list_price: %s contract.discount: %s" %(
                unit_price, contract.product.list_price, contract.list_price,
                contract.discount))
            return

        linedata = dict(
            type='line',
            product=contract.product.id,
            invoice=invoice.id,
            description="%s: %s (%s - %s)" % (contract.name, contract.product.name, last_date, next_date),
            quantity=quantity,
            unit=contract.product.default_uom.id,
            unit_price=unit_price,
            contract=contract.id,
            taxes=[],
        )

        account = contract.product.get_account([contract.product.id],'account_revenue_used')
        if account: 
            linedata['account'] = account.popitem()[1]

        tax_rule_obj = self.pool.get('account.tax.rule')
        taxes = contract.product.get_taxes([contract.product.id], 'customer_taxes_used')
        for tax in taxes[contract.product.id]:
            pattern = {}
            if contract.party.customer_tax_rule:
                tax_ids = tax_rule_obj.apply(contract.party.customer_tax_rule, tax, pattern)
                if tax_ids:
                    for tax_id in tax_ids:
                        linedata['taxes'].append(('add',tax_id))
                    continue
            linedata['taxes'].append(('add',tax))

        if contract.reference and not invoice.reference:
            invoice_obj = self.pool.get('account.invoice')
            invoice_obj.write(invoice.id, {'reference': contract.reference})

        return line_obj.create(linedata)

    def cancel_with_credit(self, ids):
        """ 
        
        Contract is canceled. Open invoices are credited.
        i.e. in case of failure to provide proper and
        valid credentials such as an initial payment.

        """
        contract_obj = self.pool.get('contract.contract')
        contract = self.browse(ids)[0]
        if not contract.state == 'active':
            return {}

        for id in ids:
            self.workflow_trigger_validate(id, 'cancel')

        invoice_obj = self.pool.get('account.invoice')
        invoice_line_obj = self.pool.get('account.invoice.line')
        line_ids = invoice_line_obj.search([('contract','in',ids)])
        if not line_ids:
            return {}

        invoices = []
        lines = invoice_line_obj.browse(line_ids)
        for l in lines:
            invoices.append(l.invoice.id)
        invoice_obj.credit(invoices, refund=True)

        return {}

    def create_next_invoice(self, ids, data=None):
        if data.get('form') and data['form'].get('invoice_date'):
            invoice_date = data['form']['invoice_date']
        else:
            invoice_date = datetime.date.today()

        contract_obj = self.pool.get('contract.contract')
        contract = self.browse(ids)[0]

        period = self._check_contract(contract, invoice_date)
        if not period: return {}

        log.debug("invoice_date: %s period: %s" %(invoice_date, period))

        ## create a new invoice
        invoice = self._invoice_init(contract, invoice_date)

        ## create invoice lines
        line = self._invoice_append(invoice, contract, period)
        self.write(contract.id, {'opt_invoice_date': period[1]})


        return invoice.id


    def _check_contract(self, contract, invoice_date):
        """
        returns tuple 'period' or False 

        False is returned if this contract is not up for billing at this 
        moment.

        period is defined as (last_date, next_date, quantity) 
        where the first two are datetime values indicating the billing period this
        contract is valid for, and the last indicates the quantity on the
        invoice line (months, weeks, years)

        """

        if not contract.state == 'active':
            return {}

        # don't invoice contracts unless they are due within 30 days
        # after the invoice_date
        end = invoice_date + datetime.timedelta(NOTICE_DAYS)
        
        if contract.next_invoice_date and end < contract.next_invoice_date:
            log.info('too early to invoice: %s + %d days < %s' %(invoice_date,
                                                                 NOTICE_DAYS,
                                                                 end))
            return False

        last_date = contract.next_invoice_date or contract.start_date or invoice_date
        next_date = last_date
        quant = 0

        while next_date < end:
            if contract.interval == 'year':
                next_date = next_date + relativedelta(years=contract.interval_quant)
                quant = relativedelta(next_date, last_date).years

            elif contract.interval == 'month':
                next_date = next_date + relativedelta(months=contract.interval_quant)
                delta = relativedelta(next_date, last_date)
                quant = delta.years * 12 + delta.months

            elif contract.interval == 'week':
                next_date = next_date + relativedelta(weeks=contract.interval_quant)
                quant = (next_date - last_date).days/7

            elif contract.interval == 'day':
                next_date = next_date + relativedelta(days=contract.interval_quant)
                quant = (next_date - last_date).days

        if next_date and contract.stop_date and next_date > contract.stop_date:
            log.info('contract stopped: %s > %s' % (next_date, contract.stop_date))
            return False

        quant = quant * contract.quantity

        log.debug("last_date: %s next_date: %s quant: %d" % (last_date,next_date,quant))
        return (last_date, next_date, quant)

    def create_invoice_batch(self, party=None, data=None):
        if data and data.get('form') and data['form'].get('invoice_date'):
            invoice_date = data['form']['invoice_date']
        else:
            invoice_date = datetime.date.today()

        log.info("create invoice batch with invoice_date: %s" % invoice_date)

        contract_obj = self.pool.get('contract.contract')

        contract_ids = None
        if data and data.get('model') == 'contract.contract':
            contract_ids = data.get('ids')
        if not contract_ids:
            """ 
            get a list of all active contracts
            """
            query = [('state','=','active'), 
                     ('start_date','<=',invoice_date),
                    ]

            """
            filter on party if required
            """
            if party:
                if type(party) != type([1,]):
                    party = [party,]
                query.append(('party','in',party))

            contract_ids = contract_obj.search(query)

            if not contract_ids:
                return []

        """
        build the list of all billable contracts
        and aggragate the result per party
        """
        batch = {}
        contracts = contract_obj.browse(contract_ids)
        for contract in contracts:
            period = self._check_contract(contract, invoice_date)
            if period and period[2]:
                key = contract.party.id
                if not batch.get(key): batch[key] = []
                batch[key].append((contract, period))

        """
        create draft invoices per party with lines
        for all billable contracts
        """
        res = []
        for party, info in batch.items():
            invoice = self._invoice_init(info[0][0], invoice_date)
            for (contract, period) in info:
                self._invoice_append(invoice, contract, period)
                self.write(contract.id, {'opt_invoice_date': period[1]})
            res.append(invoice.id)
        return res
Пример #18
0
class IncomeTaxRule(ModelSQL, ModelView):
    """  Income Tax Rules"""

    __name__ = 'income_tax.rule'

    name = fields.Char('Rule Name')
    start_date = fields.Date('Start Date')
    end_date = fields.Date('End Date')
    fiscal_year = fields.Many2One('account.fiscalyear', 'Fiscal Year')
    gender = fields.Selection([
        ('not_applicable', 'Not Applicable'),
        ('male', 'Male'),
        ('female', 'Female'),
        ('other', "Other")], 'Gender')
    born_after = fields.Date('Born after')
    born_before = fields.Date('Born before')
    cess = fields.Integer('Cess %')
    rule_lines = fields.One2Many('income_tax.slab',
                                 'income_tax_rule',
                                 'Rules')

    @staticmethod
    def default_gender():
        '''returns gender as Not applicable default value'''
        return 'not_applicable'

    @staticmethod
    def default_fiscal_year():
        '''
        returns start_date as this year's current
        fiscal year's start_date value.
        '''
        pool = Pool()
        fiscal = pool.get('account.fiscalyear')
        company = Transaction().context.get('company')
        current_fiscal_year = fiscal.find(company)
        return current_fiscal_year

    @classmethod
    def get_annual_income_tax(cls, employee, income):
        '''Calculate the income tax based on employee's record,
        current fiscal year and applicable tax slab
        '''
        projected_income_tax = 0
        taxable_income = income
        pool = Pool()
        fiscal = pool.get('account.fiscalyear')
        company = Transaction().context.get('company')
        current_fiscal_year = fiscal.find(company)
        tax_rule = pool.get('income_tax.rule')
        tax_rules = tax_rule.search([
            ('fiscal_year', '=', current_fiscal_year),
        ])
        for rule in tax_rules:
            for slab in rule.rule_lines:
                if slab.from_amount <= taxable_income and \
                        slab.to_amount >= taxable_income:
                    projected_income_tax = (slab.percentage/100)*taxable_income
                    return projected_income_tax

    @classmethod
    def get_income_tax_ytd(cls, employee, income):
        '''
        Calculate the income tax deducted based on employee's
        record, current fiscal year and applicable tax slab

        TODO: Review the docstring and method
        '''
        taxable_income_ytd = income
        pool = Pool()
        fiscal = pool.get('account.fiscalyear')
        company = Transaction().context.get('company')
        current_fiscal_year = fiscal.find(company)
        tax_rule = pool.get('income_tax.rule')
        tax_rules = tax_rule.search([
            ('fiscal_year', '=', current_fiscal_year),
        ])
        for rule in tax_rules:
            for slab in rule.rule_lines:
                if slab.from_amount <= taxable_income_ytd and \
                        slab.to_amount >= taxable_income_ytd:
                    annual_income_tax = (slab.percentage/100) \
                        * taxable_income_ytd
                    return annual_income_tax
Пример #19
0
class Book(ModelSQL, ModelView):
    'Book'
    __name__ = 'library.book'
    _rec_name = 'title'

    author = fields.Many2One('library.author',
                             'Author',
                             required=True,
                             ondelete='CASCADE')
    exemplaries = fields.One2Many('library.book.exemplary', 'book',
                                  'Exemplaries')
    title = fields.Char('Title', required=True)
    genre = fields.Many2One('library.genre',
                            'Genre',
                            ondelete='RESTRICT',
                            domain=[('editors', '=', Eval('editor'))],
                            depends=['editor'],
                            required=False)
    editor = fields.Many2One(
        'library.editor',
        'Editor',
        ondelete='RESTRICT',
        domain=[
            If(Bool(Eval('publishing_date', False)),
               [('creation_date', '<=', Eval('publishing_date'))], [])
        ],
        required=True,
        depends=['publishing_date'])
    isbn = fields.Char('ISBN',
                       size=13,
                       help='The International Standard Book Number')
    publishing_date = fields.Date('Publishing date')
    description = fields.Char('Description')
    summary = fields.Text('Summary')
    cover = fields.Binary('Cover')
    page_count = fields.Integer('Page Count',
                                help='The number of page in the book')
    edition_stopped = fields.Boolean(
        'Edition stopped',
        help='If True, this book will not be printed again in this version')
    number_of_exemplaries = fields.Function(
        fields.Integer('Number of exemplaries'),
        'getter_number_of_exemplaries')
    latest_exemplary = fields.Function(
        fields.Many2One('library.book.exemplary', 'Latest exemplary'),
        'getter_latest_exemplary')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        t = cls.__table__()
        cls._sql_constraints += [
            ('author_title_uniq', Unique(t, t.author, t.title),
             'The title must be unique per author!'),
        ]
        cls._error_messages.update({
            'invalid_isbn':
            'ISBN should only be digits',
            'bad_isbn_size':
            'ISBN must have 13 digits',
            'invalid_isbn_checksum':
            'ISBN checksum invalid',
        })
        cls._buttons.update({
            'create_exemplaries': {},
        })

    @classmethod
    def validate(cls, books):
        for book in books:
            if not book.isbn:
                continue
            try:
                if int(book.isbn) < 0:
                    raise ValueError
            except ValueError:
                cls.raise_user_error('invalid_isbn')
            if len(book.isbn) != 13:
                cls.raise_user_error('bad_isbn_size')
            checksum = 0
            for idx, digit in enumerate(book.isbn):
                checksum += int(digit) * (1 if idx % 2 else 3)
            if checksum % 10:
                cls.raise_user_error('invalid_isbn_checksum')

    @classmethod
    def default_exemplaries(cls):
        return []

    @fields.depends('editor', 'genre')
    def on_change_editor(self):
        if not self.editor:
            return
        if self.genre and self.genre not in self.editor.genres:
            self.genre = None
        if not self.genre and len(self.editor.genres) == 1:
            self.genre = self.editor.genres[0]

    @fields.depends('description', 'summary')
    def on_change_with_description(self):
        if self.description:
            return self.description
        if not self.summary:
            return ''
        return self.summary.split('.')[0]

    @fields.depends('exemplaries')
    def on_change_with_number_of_exemplaries(self):
        return len(self.exemplaries or [])

    def getter_latest_exemplary(self, name):
        latest = None
        for exemplary in self.exemplaries:
            if not exemplary.acquisition_date:
                continue
            if not latest or (latest.acquisition_date <
                              exemplary.acquisition_date):
                latest = exemplary
        return latest.id if latest else None

    @classmethod
    def getter_number_of_exemplaries(cls, books, name):
        result = {x.id: 0 for x in books}
        Exemplary = Pool().get('library.book.exemplary')
        exemplary = Exemplary.__table__()

        cursor = Transaction().connection.cursor()
        cursor.execute(
            *exemplary.select(exemplary.book,
                              Count(exemplary.id),
                              where=exemplary.book.in_([x.id for x in books]),
                              group_by=[exemplary.book]))
        for book_id, count in cursor.fetchall():
            result[book_id] = count
        return result

    @classmethod
    @ModelView.button_action('library.act_create_exemplaries')
    def create_exemplaries(cls, books):
        pass
Пример #20
0
class Product(DeactivableMixin, ModelSQL, ModelView, CompanyMultiValueMixin):
    "Product Variant"
    __name__ = "product.product"
    _order_name = 'rec_name'
    template = fields.Many2One('product.template',
                               'Product Template',
                               required=True,
                               ondelete='CASCADE',
                               select=True,
                               states=STATES,
                               depends=DEPENDS)
    code_readonly = fields.Function(fields.Boolean('Code Readonly'),
                                    'get_code_readonly')
    code = fields.Char("Code",
                       size=None,
                       select=True,
                       states={
                           'readonly':
                           STATES['readonly'] | Eval('code_readonly', False),
                       },
                       depends=DEPENDS + ['code_readonly'])
    identifiers = fields.One2Many('product.identifier',
                                  'product',
                                  "Identifiers",
                                  states=STATES,
                                  depends=DEPENDS,
                                  help="Add other identifiers to the variant.")
    cost_price = fields.MultiValue(
        fields.Numeric("Cost Price",
                       required=True,
                       digits=price_digits,
                       states=STATES,
                       depends=DEPENDS))
    cost_prices = fields.One2Many('product.cost_price', 'product',
                                  "Cost Prices")
    description = fields.Text("Description",
                              translate=True,
                              states=STATES,
                              depends=DEPENDS)
    list_price_uom = fields.Function(
        fields.Numeric('List Price', digits=price_digits), 'get_price_uom')
    cost_price_uom = fields.Function(
        fields.Numeric('Cost Price', digits=price_digits), 'get_price_uom')

    @classmethod
    def __setup__(cls):
        pool = Pool()
        Template = pool.get('product.template')

        if not hasattr(cls, '_no_template_field'):
            cls._no_template_field = set()
        cls._no_template_field.update(['products'])

        super(Product, cls).__setup__()

        for attr in dir(Template):
            tfield = getattr(Template, attr)
            if not isinstance(tfield, fields.Field):
                continue
            if attr in cls._no_template_field:
                continue
            field = getattr(cls, attr, None)
            if not field or isinstance(field, TemplateFunction):
                setattr(cls, attr, TemplateFunction(copy.deepcopy(tfield)))
                order_method = getattr(cls, 'order_%s' % attr, None)
                if (not order_method and not isinstance(
                        tfield,
                    (fields.Function, fields.One2Many, fields.Many2Many))):
                    order_method = TemplateFunction.order(attr)
                    setattr(cls, 'order_%s' % attr, order_method)
                if isinstance(tfield, fields.One2Many):
                    getattr(cls, attr).setter = '_set_template_function'

    @classmethod
    def _set_template_function(cls, products, name, value):
        # Prevent NotImplementedError for One2Many
        pass

    @fields.depends('template', '_parent_template.id')
    def on_change_template(self):
        for name, field in self._fields.items():
            if isinstance(field, TemplateFunction):
                if self.template:
                    value = getattr(self.template, name, None)
                else:
                    value = None
                setattr(self, name, value)

    def get_template(self, name):
        value = getattr(self.template, name)
        if isinstance(value, Model):
            return value.id
        elif (isinstance(value, (list, tuple)) and value
              and isinstance(value[0], Model)):
            return [r.id for r in value]
        else:
            return value

    @classmethod
    def multivalue_model(cls, field):
        pool = Pool()
        if field == 'cost_price':
            return pool.get('product.cost_price')
        return super(Product, cls).multivalue_model(field)

    @classmethod
    def default_cost_price(cls, **pattern):
        return Decimal(0)

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

    @classmethod
    def order_rec_name(cls, tables):
        pool = Pool()
        Template = pool.get('product.template')
        product, _ = tables[None]
        if 'template' not in tables:
            template = Template.__table__()
            tables['template'] = {
                None: (template, product.template == template.id),
            }
        else:
            template = tables['template']
        return [product.code] + Template.name.convert_order(
            'name', tables['template'], Template)

    def get_rec_name(self, name):
        if self.code:
            return '[' + self.code + '] ' + self.name
        else:
            return self.name

    @classmethod
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        code_value = clause[2]
        if clause[1].endswith('like'):
            code_value = lstrip_wildcard(clause[2])
        return [
            bool_op,
            ('code', clause[1], code_value) + tuple(clause[3:]),
            ('identifiers.code', clause[1], code_value) + tuple(clause[3:]),
            ('template.name', ) + tuple(clause[1:]),
        ]

    @staticmethod
    def get_price_uom(products, name):
        Uom = Pool().get('product.uom')
        res = {}
        field = name[:-4]
        if Transaction().context.get('uom'):
            to_uom = Uom(Transaction().context['uom'])
        else:
            to_uom = None
        for product in products:
            price = getattr(product, field)
            if to_uom and product.default_uom.category == to_uom.category:
                res[product.id] = Uom.compute_price(product.default_uom, price,
                                                    to_uom)
            else:
                res[product.id] = price
        return res

    @classmethod
    def search_global(cls, text):
        for id_, rec_name, icon in super(Product, cls).search_global(text):
            icon = icon or 'tryton-product'
            yield id_, rec_name, icon

    @classmethod
    def default_code_readonly(cls):
        pool = Pool()
        Configuration = pool.get('product.configuration')
        config = Configuration(1)
        return bool(config.product_sequence)

    def get_code_readonly(self, name):
        return self.default_code_readonly()

    @classmethod
    def _new_code(cls):
        pool = Pool()
        Sequence = pool.get('ir.sequence')
        Configuration = pool.get('product.configuration')
        config = Configuration(1)
        sequence = config.product_sequence
        if sequence:
            return Sequence.get_id(sequence.id)

    @classmethod
    def create(cls, vlist):
        vlist = [x.copy() for x in vlist]
        for values in vlist:
            if not values.get('code'):
                values['code'] = cls._new_code()
        return super().create(vlist)

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

    @property
    def list_price_used(self):
        return self.template.get_multivalue('list_price')
Пример #21
0
class PaymentTerm(ModelSQL, ModelView):
    'Payment Term'
    __name__ = 'account.invoice.payment_term'
    name = fields.Char('Name', size=None, required=True, translate=True)
    active = fields.Boolean('Active')
    description = fields.Text('Description', translate=True)
    lines = fields.One2Many('account.invoice.payment_term.line', 'payment',
                            'Lines')

    @classmethod
    def __setup__(cls):
        super(PaymentTerm, cls).__setup__()
        cls._order.insert(0, ('name', 'ASC'))
        cls._error_messages.update({
            'invalid_line': ('Invalid line "%(line)s" in payment term '
                             '"%(term)s".'),
            'missing_remainder': ('Missing remainder line in payment term '
                                  '"%s".'),
            'last_remainder': ('Last line of payment term "%s" must be of '
                               'type remainder.'),
        })

    @classmethod
    def validate(cls, terms):
        super(PaymentTerm, cls).validate(terms)
        for term in terms:
            term.check_remainder()

    def check_remainder(self):
        if not self.lines or not self.lines[-1].type == 'remainder':
            self.raise_user_error('last_remainder', self.rec_name)

    @staticmethod
    def default_active():
        return True

    def compute(self, amount, currency, date=None):
        """Calculate payment terms and return a list of tuples
        with (date, amount) for each payment term line.

        amount must be a Decimal used for the calculation.
        If specified, date will be used as the start date, otherwise current
        date will be used.
        """
        # TODO implement business_days
        # http://pypi.python.org/pypi/BusinessHours/
        Date = Pool().get('ir.date')

        sign = 1 if amount >= Decimal('0.0') else -1
        res = []
        if date is None:
            date = Date.today()
        remainder = amount
        for line in self.lines:
            value = line.get_value(remainder, amount, currency)
            value_date = line.get_date(date)
            if not value or not value_date:
                if (not remainder) and line.amount:
                    self.raise_user_error('invalid_line', {
                        'line': line.rec_name,
                        'term': self.rec_name,
                    })
                else:
                    continue
            if ((remainder - value) * sign) < Decimal('0.0'):
                res.append((value_date, remainder))
                break
            res.append((value_date, value))
            remainder -= value

        if not currency.is_zero(remainder):
            self.raise_user_error('missing_remainder', (self.rec_name, ))
        return res
Пример #22
0
class Template(DeactivableMixin, ModelSQL, ModelView, CompanyMultiValueMixin):
    "Product Template"
    __name__ = "product.template"
    name = fields.Char('Name',
                       size=None,
                       required=True,
                       translate=True,
                       select=True,
                       states=STATES,
                       depends=DEPENDS)
    type = fields.Selection(TYPES,
                            'Type',
                            required=True,
                            states=STATES,
                            depends=DEPENDS)
    consumable = fields.Boolean('Consumable',
                                states={
                                    'readonly': ~Eval('active', True),
                                    'invisible':
                                    Eval('type', 'goods') != 'goods',
                                },
                                depends=['active', 'type'])
    list_price = fields.MultiValue(
        fields.Numeric("List Price",
                       required=True,
                       digits=price_digits,
                       states=STATES,
                       depends=DEPENDS))
    list_prices = fields.One2Many('product.list_price', 'template',
                                  "List Prices")
    cost_price = fields.Function(
        fields.Numeric("Cost Price", digits=price_digits), 'get_cost_price')
    cost_price_method = fields.MultiValue(
        fields.Selection(COST_PRICE_METHODS,
                         "Cost Price Method",
                         required=True,
                         states=STATES,
                         depends=DEPENDS))
    cost_price_methods = fields.One2Many('product.cost_price_method',
                                         'template', "Cost Price Methods")
    default_uom = fields.Many2One('product.uom',
                                  'Default UOM',
                                  required=True,
                                  states=STATES,
                                  depends=DEPENDS)
    default_uom_category = fields.Function(
        fields.Many2One('product.uom.category', 'Default UOM Category'),
        'on_change_with_default_uom_category',
        searcher='search_default_uom_category')
    categories = fields.Many2Many('product.template-product.category',
                                  'template',
                                  'category',
                                  'Categories',
                                  states=STATES,
                                  depends=DEPENDS)
    categories_all = fields.Many2Many('product.template-product.category.all',
                                      'template',
                                      'category',
                                      "Categories",
                                      readonly=True)
    products = fields.One2Many('product.product',
                               'template',
                               'Variants',
                               states=STATES,
                               depends=DEPENDS)

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

        table = cls.__table_handler__(module_name)

        # Migration from 3.8: rename category into categories
        if table.column_exist('category'):
            logger.warning(
                'The column "category" on table "%s" must be dropped manually',
                cls._table)

    @classmethod
    def multivalue_model(cls, field):
        pool = Pool()
        if field == 'list_price':
            return pool.get('product.list_price')
        elif field == 'cost_price_method':
            return pool.get('product.cost_price_method')
        return super(Template, cls).multivalue_model(field)

    @staticmethod
    def default_type():
        return 'goods'

    @staticmethod
    def default_consumable():
        return False

    def get_cost_price(self, name):
        if len(self.products) == 1:
            product, = self.products
            return product.cost_price

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

    @staticmethod
    def default_products():
        if Transaction().user == 0:
            return []
        return [{}]

    @fields.depends('type', 'cost_price_method')
    def on_change_type(self):
        if self.type == 'service':
            self.cost_price_method = 'fixed'

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

    @classmethod
    def search_default_uom_category(cls, name, clause):
        return [('default_uom.category' + clause[0].lstrip(name), ) +
                tuple(clause[1:])]

    @classmethod
    def create(cls, vlist):
        vlist = [v.copy() for v in vlist]
        for values in vlist:
            values.setdefault('products', None)
        return super(Template, cls).create(vlist)

    @classmethod
    def search_global(cls, text):
        for record, rec_name, icon in super(Template, cls).search_global(text):
            icon = icon or 'tryton-product'
            yield record, rec_name, icon
Пример #23
0
class CrmSegmentation(ModelSQL, ModelView):
    '''
        A segmentation is a tool to automatically assign categories on partys.
        These assignations are based on criterions.
    '''
    _name = "ekd.crm.segmentation"
    _description = "Party Segmentation"

    name = fields.Char('Name', size=64, required=True, help='The name of the segmentation.')
    description = fields.Text('Description')
    categ = fields.Many2One('party.category', 'party Category', required=True, help='The party category that will be added to partys that match the segmentation criterions after computation.')
    exclusif = fields.Boolean('Exclusive', help='Check if the category is limited to partys that match the segmentation criterions. If checked, remove the category from partys that doesn\'t match segmentation criterions')
    state = fields.Selection([('not running','Not Running'),('running','Running')], 'Execution Status', readonly=True)
    party = fields.Integer('Max party ID processed')
    segmentation_line = fields.One2Many('ekd.crm.segmentation.line', 'segmentation', 'Criteria', required=True)
    som_interval = fields.Integer('Days per Periode', help="A period is the average number of days between two cycle of sale or purchase for this segmentation. It's mainly used to detect if a party has not purchased or buy for a too long time, so we suppose that his state of mind has decreased because he probably bought goods to another supplier. Use this functionality for recurring businesses.")
    som_interval_max = fields.Integer('Max Interval', help="The computation is made on all events that occured during this interval, the past X periods.")
    som_interval_decrease = fields.Float('Decrease (0>1)', help="If the party has not purchased (or bought) during a period, decrease the state of mind by this factor. It\'s a multiplication")
    som_interval_default = fields.Float('Default (0=None)', help="Default state of mind for period preceeding the 'Max Interval' computation. This is the starting state of mind by default if the party has no event.")
    sales_purchase_active = fields.Boolean('Use The Sales Purchase Rules', help='Check if you want to use this tab as part of the segmentation rule. If not checked, the criteria beneath will be ignored')

    def __init__(self):
        super(CrmSegmentation, self).__init__()

        self._rpc.update({
            'button_process_start': True,
            'button_process_stop': True,
            'button_process_continue': True,
            })

    def default_party(self):
        return 0

    def default_state(self):
        return 'not running'

    def default_som_interval_max(self):
        return 3

    def default_som_interval_decrease(self):
        return Decimal('0.8')

    def default_som_interval_default(self):
        return Decimal('0.5')

    def button_process_continue(self, ids, start=False):
        cr = Transaction().cursor
        categs = self.read(ids,['category','exclusif','party', 'sales_purchase_active', 'profiling_active'])
        for categ in categs:
            if start:
                if categ['exclusif']:
                    cr.execute('delete from party_category_rel where category=%s', (categ['categ'][0],))

            id = categ['id']

            cr.execute('select id from res_party order by id ')
            partys = [x[0] for x in cr.fetchall()]

            if categ['sales_purchase_active']:
                to_remove_list=[]
                cr.execute('select id from ekd_crm_segmentation_line where segmentation=%s', (id,))
                line_ids = [x[0] for x in cr.fetchall()]

                for pid in partys:
                    if (not self.pool.get('ekd.crm.segmentation.line').test(cr, uid, line_ids, pid)):
                        to_remove_list.append(pid)
                for pid in to_remove_list:
                    partys.remove(pid)

            for party in partys:
                cr.execute('insert into party_category_rel (category,party) values (%s,%s)', (categ['categ'][0],party))
            cr.commit()

            self.write([id], {'state':'not running', 'party':0})
            cr.commit()
        return True

    def button_process_stop(self, ids, *args):
        return self.write(ids, {'state':'not running', 'party':0})

    def button_process_start(self, ids, *args):
        self.write(ids, {'state':'running', 'party':0})
        return self.process_continue(cr, uid, ids, start=True)
Пример #24
0
class Procedure(ModelSQL, ModelView):
    'Account Dunning Procedure'
    __name__ = 'account.dunning.procedure'
    name = fields.Char('Name', required=True, translate=True)
    levels = fields.One2Many('account.dunning.level', 'procedure', 'Levels')
Пример #25
0
class Party(CompanyMultiValueMixin, metaclass=PoolMeta):
    __name__ = 'party.party'
    accounts = fields.One2Many('party.party.account', 'party', "Accounts")
    account_payable = fields.MultiValue(
        fields.Many2One('account.account',
                        "Account Payable",
                        domain=[
                            ('kind', '=', 'payable'),
                            ('party_required', '=', True),
                            ('company', '=', Eval('context',
                                                  {}).get('company', -1)),
                        ],
                        states={
                            'invisible': ~Eval('context', {}).get('company'),
                        }))
    account_receivable = fields.MultiValue(
        fields.Many2One('account.account',
                        "Account Receivable",
                        domain=[
                            ('kind', '=', 'receivable'),
                            ('party_required', '=', True),
                            ('company', '=', Eval('context',
                                                  {}).get('company', -1)),
                        ],
                        states={
                            'invisible': ~Eval('context', {}).get('company'),
                        }))
    customer_tax_rule = fields.MultiValue(
        fields.Many2One(
            'account.tax.rule',
            "Customer Tax Rule",
            domain=[
                ('company', '=', Eval('context', {}).get('company', -1)),
                ('kind', 'in', ['sale', 'both']),
            ],
            states={
                'invisible': ~Eval('context', {}).get('company'),
            },
            help='Apply this rule on taxes when party is customer.'))
    supplier_tax_rule = fields.MultiValue(
        fields.Many2One(
            'account.tax.rule',
            "Supplier Tax Rule",
            domain=[
                ('company', '=', Eval('context', {}).get('company', -1)),
                ('kind', 'in', ['purchase', 'both']),
            ],
            states={
                'invisible': ~Eval('context', {}).get('company'),
            },
            help='Apply this rule on taxes when party is supplier.'))
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'get_currency_digits')
    receivable = fields.Function(fields.Numeric(
        'Receivable',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
                                 'get_receivable_payable',
                                 searcher='search_receivable_payable')
    payable = fields.Function(fields.Numeric(
        'Payable',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
                              'get_receivable_payable',
                              searcher='search_receivable_payable')
    receivable_today = fields.Function(fields.Numeric(
        'Receivable Today',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
                                       'get_receivable_payable',
                                       searcher='search_receivable_payable')
    payable_today = fields.Function(fields.Numeric(
        'Payable Today',
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits']),
                                    'get_receivable_payable',
                                    searcher='search_receivable_payable')

    @classmethod
    def __setup__(cls):
        super(Party, cls).__setup__()
        cls._error_messages.update({
            'missing_receivable_account':
            ('There is no receivable account on party "%(name)s".'),
            'missing_payable_account':
            ('There is no payable account on party "%(name)s".'),
        })

    @classmethod
    def multivalue_model(cls, field):
        pool = Pool()
        if field in account_names:
            return pool.get('party.party.account')
        return super(Party, cls).multivalue_model(field)

    @classmethod
    def get_currency_digits(cls, parties, name):
        pool = Pool()
        Company = pool.get('company.company')
        company_id = Transaction().context.get('company')
        if company_id:
            company = Company(company_id)
            digits = company.currency.digits
        else:
            digits = 2
        return {p.id: digits for p in parties}

    @classmethod
    def get_receivable_payable(cls, parties, names):
        '''
        Function to compute receivable, payable (today or not) for party ids.
        '''
        result = {}
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        Account = pool.get('account.account')
        User = pool.get('res.user')
        Date = pool.get('ir.date')
        cursor = Transaction().connection.cursor()

        line = MoveLine.__table__()
        account = Account.__table__()

        for name in names:
            if name not in ('receivable', 'payable', 'receivable_today',
                            'payable_today'):
                raise Exception('Bad argument')
            result[name] = dict((p.id, Decimal('0.0')) for p in parties)

        user = User(Transaction().user)
        if not user.company:
            return result
        company_id = user.company.id
        exp = Decimal(str(10.0**-user.company.currency.digits))

        amount = Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0))
        for name in names:
            code = name
            today_where = Literal(True)
            if name in ('receivable_today', 'payable_today'):
                code = name[:-6]
                today_where = ((line.maturity_date <= Date.today())
                               | (line.maturity_date == Null))
            for sub_parties in grouped_slice(parties):
                sub_ids = [p.id for p in sub_parties]
                party_where = reduce_ids(line.party, sub_ids)
                cursor.execute(
                    *line.join(account, condition=account.id == line.account).
                    select(line.party,
                           amount,
                           where=((account.kind == code)
                                  & (line.reconciliation == Null)
                                  & (account.company == company_id)
                                  & party_where
                                  & today_where),
                           group_by=line.party))
                for party, value in cursor.fetchall():
                    # SQLite uses float for SUM
                    if not isinstance(value, Decimal):
                        value = Decimal(str(value))
                    result[name][party] = value.quantize(exp)
        return result

    @classmethod
    def search_receivable_payable(cls, name, clause):
        pool = Pool()
        MoveLine = pool.get('account.move.line')
        Account = pool.get('account.account')
        User = pool.get('res.user')
        Date = pool.get('ir.date')

        line = MoveLine.__table__()
        account = Account.__table__()

        if name not in ('receivable', 'payable', 'receivable_today',
                        'payable_today'):
            raise Exception('Bad argument')
        _, operator, value = clause

        user = User(Transaction().user)
        if not user.company:
            return []
        company_id = user.company.id

        code = name
        today_query = Literal(True)
        if name in ('receivable_today', 'payable_today'):
            code = name[:-6]
            today_query = ((line.maturity_date <= Date.today())
                           | (line.maturity_date == Null))

        Operator = fields.SQL_OPERATORS[operator]

        # Need to cast numeric for sqlite
        cast_ = MoveLine.debit.sql_cast
        amount = cast_(Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)))
        if operator in {'in', 'not in'}:
            value = [cast_(Literal(Decimal(v or 0))) for v in value]
        else:
            value = cast_(Literal(Decimal(value or 0)))
        query = line.join(account,
                          condition=account.id == line.account).select(
                              line.party,
                              where=(account.kind == code)
                              & (line.party != Null)
                              & (line.reconciliation == Null)
                              & (account.company == company_id)
                              & today_query,
                              group_by=line.party,
                              having=Operator(amount, value))
        return [('id', 'in', query)]

    @property
    def account_payable_used(self):
        pool = Pool()
        Configuration = pool.get('account.configuration')
        account = self.account_payable
        if not account:
            config = Configuration(1)
            account = config.get_multivalue('default_account_payable')
        # Allow empty values on on_change
        if not account and not Transaction().readonly:
            self.raise_user_error('missing_payable_account', {
                'name': self.rec_name,
            })
        if account:
            return account.current()

    @property
    def account_receivable_used(self):
        pool = Pool()
        Configuration = pool.get('account.configuration')
        account = self.account_receivable
        if not account:
            config = Configuration(1)
            account = config.get_multivalue('default_account_receivable')
        # Allow empty values on on_change
        if not account and not Transaction().readonly:
            self.raise_user_error('missing_receivable_account', {
                'name': self.rec_name,
            })
        if account:
            return account.current()
Пример #26
0
class CreateShipmentInReturn(ModelView):
    'CreateShipmentInReturn'
    __name__ = 'hrp_purchase_request.CreateShipmentInReturn'
    _rec_name = 'number'
    effective_date = fields.Date('Effective Date',
                                 states={
                                     'readonly':
                                     Eval('state').in_(['cancel', 'done']),
                                 },
                                 depends=['state'])
    planned_date = fields.Date('Planned Date',
                               states={
                                   'readonly': Eval('state') != 'draft',
                               },
                               depends=['state'])
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        states={
            'readonly': Eval('state') != 'draft',
        },
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        depends=['state'])
    number = fields.Char('Number', size=None, select=True, readonly=True)
    reference = fields.Char("Reference",
                            size=None,
                            select=True,
                            states={
                                'readonly': Eval('state') != 'draft',
                            },
                            depends=['state'])
    supplier = fields.Many2One('party.party',
                               'Supplier',
                               states={
                                   'readonly': (((Eval('state') != 'draft')
                                                 | Eval('moves', [0]))
                                                & Eval('supplier', 0)),
                               },
                               required=True,
                               depends=['state', 'supplier'])
    delivery_address = fields.Many2One('party.address',
                                       'Delivery Address',
                                       states={
                                           'readonly':
                                           Eval('state') != 'draft',
                                       },
                                       domain=[('party', '=', Eval('supplier'))
                                               ],
                                       depends=['state', 'supplier'])
    from_location = fields.Many2One(
        'stock.location',
        "From Location",
        required=True,
        states={
            'readonly': (Eval('state') != 'draft') | Eval('moves', [0]),
        },
        domain=[('type', 'in', ['storage', 'view'])],
        depends=['state'])
    to_location = fields.Many2One('stock.location',
                                  "To Location",
                                  required=True,
                                  states={
                                      'readonly': (Eval('state') != 'draft')
                                      | Eval('moves', [0]),
                                  },
                                  domain=[('type', '=', 'supplier')],
                                  depends=['state'])
    moves = fields.One2Many(
        'stock.move',
        'shipment',
        'Moves',
        states={
            'readonly': (((Eval('state') != 'draft') | ~Eval('from_location'))
                         & Eval('to_location')),
        },
        domain=[
            ('from_location', '=', Eval('from_location')),
            ('to_location', '=', Eval('to_location')),
            ('company', '=', Eval('company')),
        ],
        depends=['state', 'from_location', 'to_location', 'company'])
    origins = fields.Function(fields.Char('Origins'), 'get_origins')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('cancel', 'Canceled'),
        ('assigned', 'Assigned'),
        ('waiting', 'Waiting'),
        ('done', 'Done'),
    ],
                             'State',
                             readonly=True)

    @staticmethod
    def default_planned_date():
        return Pool().get('ir.date').today()

    @staticmethod
    def default_state():
        return 'draft'

    @classmethod
    def default_warehouse(cls):
        Location = Pool().get('stock.location')
        locations = Location.search(cls.warehouse.domain)
        if len(locations) == 1:
            return locations[0].id

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

    @fields.depends('supplier')
    def on_change_supplier(self):
        self.contact_address = None
        if self.supplier:
            self.contact_address = self.supplier.address_get()

    @fields.depends('supplier')
    def on_change_with_supplier_location(self, name=None):
        if self.supplier:
            return self.supplier.supplier_location.id

    @classmethod
    def default_warehouse_input(cls):
        warehouse = cls.default_warehouse()
        if warehouse:
            return cls(warehouse=warehouse).on_change_with_warehouse_input()

    @fields.depends('warehouse')
    def on_change_with_warehouse_input(self, name=None):
        if self.warehouse:
            return self.warehouse.input_location.id

    @classmethod
    def default_warehouse_storage(cls):
        warehouse = cls.default_warehouse()
        if warehouse:
            return cls(warehouse=warehouse).on_change_with_warehouse_storage()

    @fields.depends('warehouse')
    def on_change_with_warehouse_storage(self, name=None):
        if self.warehouse:
            return self.warehouse.storage_location.id

    def get_incoming_moves(self, name):
        moves = []
        for move in self.moves:
            if move.to_location.id == self.warehouse.input_location.id:
                moves.append(move.id)
        return moves

    @classmethod
    def set_incoming_moves(cls, shipments, name, value):
        if not value:
            return
        cls.write(shipments, {
            'moves': value,
        })

    def get_inventory_moves(self, name):
        moves = []
        for move in self.moves:
            if (move.from_location.id == self.warehouse.input_location.id):
                moves.append(move.id)
        return moves

    @classmethod
    def set_inventory_moves(cls, shipments, name, value):
        if not value:
            return
        cls.write(shipments, {
            'moves': value,
        })

    @property
    def _move_planned_date(self):
        '''
        Return the planned date for incoming moves and inventory_moves
        '''
        return self.planned_date, self.planned_date

    def get_origins(self, name):
        return ', '.join(
            set(itertools.ifilter(None, (m.origin_name for m in self.moves))))

    @classmethod
    def _get_inventory_moves(cls, incoming_move):
        pool = Pool()
        Move = pool.get('stock.move')
        if incoming_move.quantity <= 0.0:
            return None
        move = Move()
        move.product = incoming_move.product
        move.uom = incoming_move.uom
        move.quantity = incoming_move.quantity
        move.from_location = incoming_move.to_location
        move.to_location = incoming_move.shipment.warehouse.storage_location
        move.state = Move.default_state()
        # Product will be considered in stock only when the inventory
        # move will be made:
        move.planned_date = None
        move.company = incoming_move.company
        return move

    @classmethod
    def create_inventory_moves(cls, shipments):
        for shipment in shipments:
            # Use moves instead of inventory_moves because save reset before
            # adding new records and as set_inventory_moves is just a proxy to
            # moves, it will reset also the incoming_moves
            moves = list(shipment.moves)
            for incoming_move in shipment.incoming_moves:
                move = cls._get_inventory_moves(incoming_move)
                if move:
                    moves.append(move)
            shipment.moves = moves
            shipment.save()
class AccountVoucher(metaclass=PoolMeta):
    __name__ = 'account.voucher'

    retenciones_efectuadas = fields.One2Many(
        'account.retencion.efectuada',
        'voucher',
        'Retenciones Efectuadas',
        states={
            'invisible':
            Eval('voucher_type') != 'payment',
            'readonly':
            Or(Eval('state') == 'posted',
               Eval('currency_code') != 'ARS'),
        },
        depends=['voucher_type', 'state', 'currency_code'])
    retenciones_soportadas = fields.One2Many(
        'account.retencion.soportada',
        'voucher',
        'Retenciones Soportadas',
        states={
            'invisible':
            Eval('voucher_type') != 'receipt',
            'readonly':
            Or(Eval('state') == 'posted',
               Eval('currency_code') != 'ARS'),
        },
        depends=['voucher_type', 'state', 'currency_code'])

    @fields.depends('retenciones_efectuadas', 'retenciones_soportadas')
    def on_change_with_amount(self, name=None):
        amount = super().on_change_with_amount(name)
        if self.retenciones_efectuadas:
            for retencion in self.retenciones_efectuadas:
                if retencion.amount:
                    amount += retencion.amount
        if self.retenciones_soportadas:
            for retencion in self.retenciones_soportadas:
                if retencion.amount:
                    amount += retencion.amount
        return amount

    def prepare_move_lines(self):
        move_lines = super().prepare_move_lines()
        Period = Pool().get('account.period')
        if self.voucher_type == 'receipt':
            if self.retenciones_soportadas:
                for retencion in self.retenciones_soportadas:
                    move_lines.append({
                        'debit':
                        retencion.amount,
                        'credit':
                        Decimal('0.0'),
                        'account':
                        (retencion.tax.account.id if retencion.tax else None),
                        'move':
                        self.move.id,
                        'journal':
                        self.journal.id,
                        'period':
                        Period.find(self.company.id, date=self.date),
                    })

        if self.voucher_type == 'payment':
            if self.retenciones_efectuadas:
                for retencion in self.retenciones_efectuadas:
                    move_lines.append({
                        'debit':
                        Decimal('0.0'),
                        'credit':
                        retencion.amount,
                        'account':
                        (retencion.tax.account.id if retencion.tax else None),
                        'move':
                        self.move.id,
                        'journal':
                        self.journal.id,
                        'period':
                        Period.find(self.company.id, date=self.date),
                    })

        return move_lines

    @classmethod
    @ModelView.button
    def post(cls, vouchers):
        pool = Pool()
        RetencionSoportada = pool.get('account.retencion.soportada')
        RetencionEfectuada = pool.get('account.retencion.efectuada')

        super().post(vouchers)

        for voucher in vouchers:
            if voucher.retenciones_soportadas:
                RetencionSoportada.write(list(voucher.retenciones_soportadas),
                                         {
                                             'party': voucher.party.id,
                                             'state': 'held',
                                         })
            if voucher.retenciones_efectuadas:
                for retencion in voucher.retenciones_efectuadas:
                    if not retencion.tax.sequence:
                        raise UserError(
                            gettext(
                                'account_retencion_ar.msg_missing_retencion_seq'
                            ))

                    RetencionEfectuada.write(
                        [retencion], {
                            'party': voucher.party.id,
                            'name': retencion.tax.sequence.get(),
                            'state': 'issued',
                        })

    @classmethod
    @ModelView.button
    def cancel(cls, vouchers):
        pool = Pool()
        RetencionSoportada = pool.get('account.retencion.soportada')
        RetencionEfectuada = pool.get('account.retencion.efectuada')

        super().cancel(vouchers)

        for voucher in vouchers:
            if voucher.retenciones_soportadas:
                RetencionSoportada.write(list(voucher.retenciones_soportadas),
                                         {
                                             'party': None,
                                             'state': 'cancelled',
                                         })
            if voucher.retenciones_efectuadas:
                RetencionEfectuada.write(list(voucher.retenciones_efectuadas),
                                         {
                                             'party': None,
                                             'state': 'cancelled',
                                         })
Пример #28
0
class TriageEntry(ModelSQL, ModelView):
    'Triage Entry'
    __name__ = 'gnuhealth.triage.entry'
    firstname = fields.Char('First Name', states=REQD_IF_NOPATIENT)
    lastname = fields.Char('Last Name', states=REQD_IF_NOPATIENT)
    sex = fields.Selection([(None, '')] + SEX_OPTIONS,
                           'Sex',
                           states=REQD_IF_NOPATIENT)
    age = fields.Char('Age', states=REQD_IF_NOPATIENT)
    sex_display = fields.Function(fields.Selection(SEX_OPTIONS, 'Sex'),
                                  'get_sex_age_display')
    age_display = fields.Function(fields.Char('Age'), 'get_sex_age_display')
    id_type = fields.Selection(ID_TYPES,
                               'ID Type',
                               states={
                                   'required': Bool(Eval('id_number')),
                                   'readonly': Bool(Eval('patient'))
                               },
                               sort=False)
    id_number = fields.Char(
        'ID Number',
        states={'readonly': Or(Bool(Eval('patient')), Eval('done', False))})
    id_display = fields.Function(fields.Char('UPI/MRN'),
                                 'get_id_display',
                                 searcher='search_id')
    patient = fields.Many2One('gnuhealth.patient',
                              'Patient',
                              states={
                                  'readonly':
                                  Or(~Eval('can_do_details', False),
                                     Eval('done', False))
                              })
    priority = fields.Selection(TRIAGE_PRIO,
                                'ESI Priority',
                                sort=False,
                                help='Emergency Severity Index Triage Level',
                                states={
                                    'invisible':
                                    ~(Eval('id', 0) > 0),
                                    'readonly':
                                    Or(~Eval('can_do_details', False),
                                       Eval('done', False))
                                })
    medical_alert = fields.Function(fields.Boolean(
        'Medical Alert',
        states={
            'invisible':
            Or(Eval('can_do_details',
                    False), ~In(Eval('status'), ['triage', 'pending']),
               ~In(Eval('priority'), ['99', '77']))
        }),
                                    'get_medical_alert',
                                    setter='set_medical_alert')
    injury = fields.Boolean('Injury', states=SIGNED_STATES)
    review = fields.Boolean('Review', states=SIGNED_STATES)
    status = fields.Selection(TRIAGE_STATUS,
                              'Status',
                              sort=False,
                              states={
                                  'readonly':
                                  Or(~Eval('can_do_details', False),
                                     Eval('done', False))
                              })
    status_display = fields.Function(fields.Char('Status'),
                                     'get_status_display')
    complaint = fields.Char('Primary Complaint', states=SIGNED_STATES)
    notes = fields.Text('Notes (edit)', states=SIGNED_STATES)
    note_entries = fields.One2Many('gnuhealth.triage.note', 'triage_entry',
                                   'Note entries')
    note_display = fields.Function(fields.Text('Notes'), 'get_note_display')
    upi = fields.Function(fields.Char('UPI'), 'get_patient_party_field')
    name = fields.Function(fields.Char('Name'),
                           'get_name',
                           searcher='search_name')
    patient_search = fields.Function(
        fields.One2Many('gnuhealth.patient', None, 'Patients'),
        'patient_search_result')
    queue_entry = fields.One2Many('gnuhealth.patient.queue_entry',
                                  'triage_entry',
                                  'Queue Entry',
                                  size=1)
    encounter = fields.Many2One('gnuhealth.encounter', 'Encounter')
    # Vital Signs
    systolic = fields.Integer('Systolic Pressure', states=SIGNED_STATES)
    diastolic = fields.Integer('Diastolic Pressure', states=SIGNED_STATES)
    bpm = fields.Integer('Heart Rate (bpm)',
                         help='Heart rate expressed in beats per minute',
                         states=SIGNED_STATES)
    respiratory_rate = fields.Integer(
        'Respiratory Rate',
        help='Respiratory rate expressed in breaths per minute',
        states=SIGNED_STATES)
    osat = fields.Integer('Oxygen Saturation',
                          help='Oxygen Saturation(arterial).',
                          states=SIGNED_STATES)
    temperature = fields.Float(u'Temperature (°C)',
                               digits=(4, 1),
                               help='Temperature in degrees celsius',
                               states=SIGNED_STATES)
    # domain=[('temperature', '>', 25), ('temperature', '<', 50)])
    childbearing_age = fields.Function(fields.Boolean('Childbearing Age'),
                                       'get_childbearing_age')
    pregnant = fields.Boolean('Pregnant', states=STATE_NO_MENSES)
    lmp = fields.Date('Last Menstrual Period',
                      states=STATE_NO_MENSES,
                      help='Date last menstrual period started')
    glucose = fields.Float(
        'Glucose (mmol/l)',
        digits=(5, 1),
        help='mmol/l. Reading from glucose meter',
        states=SIGNED_STATES,
        domain=[
            'OR', ('glucose', '=', None),
            ['AND', ('glucose', '>', 0), ('glucose', '<', 55.1)]
        ])
    height = fields.Numeric('Height (cm)', digits=(4, 1), states=SIGNED_STATES)
    weight = fields.Numeric('Weight (kg)', digits=(3, 2), states=SIGNED_STATES)
    uri_ph = fields.Numeric('pH', digits=(1, 1), states=SIGNED_STATES)
    uri_specific_gravity = fields.Numeric('Specific Gravity',
                                          digits=(1, 3),
                                          states=SIGNED_STATES)
    uri_protein = fields.Selection('uri_selection',
                                   'Protein',
                                   sort=False,
                                   states=SIGNED_STATES)
    uri_blood = fields.Selection('uri_selection',
                                 'Blood',
                                 sort=False,
                                 states=SIGNED_STATES)
    uri_glucose = fields.Selection('uri_selection',
                                   'Glucose',
                                   sort=False,
                                   states=SIGNED_STATES)
    uri_nitrite = fields.Selection('uri_nitrite_selection',
                                   'Nitrite',
                                   sort=False,
                                   states=SIGNED_STATES)
    uri_bilirubin = fields.Selection('uri_selection',
                                     'Bilirubin',
                                     sort=False,
                                     states=SIGNED_STATES)
    uri_leuko = fields.Selection('uri_selection',
                                 'Leukocytes',
                                 sort=False,
                                 states=SIGNED_STATES)
    uri_ketone = fields.Selection('uri_selection',
                                  'Ketone',
                                  sort=False,
                                  states=SIGNED_STATES)
    uri_urobili = fields.Selection('uri_selection',
                                   'Urobilinogen',
                                   sort=False,
                                   states=SIGNED_STATES)

    malnutrition = fields.Boolean(
        'Malnourished',
        help='Check this box if the patient show signs of malnutrition.',
        states=SIGNED_STATES)

    dehydration = fields.Selection(
        [(None, 'No'), ('mild', 'Mild'), ('moderate', 'Moderate'),
         ('severe', 'Severe')],
        'Dehydration',
        sort=False,
        help='If the patient show signs of dehydration.',
        states=SIGNED_STATES)
    symp_fever = fields.Boolean('Fever', states=SIGNED_STATES)
    symp_respiratory = fields.Boolean('Respiratory',
                                      help="breathing problems",
                                      states=SIGNED_STATES)
    symp_jaundice = fields.Boolean('Jaundice', states=SIGNED_STATES)
    symp_rash = fields.Boolean('Rash', states=SIGNED_STATES)
    symp_hemorrhagic = fields.Boolean("Hemorrhagic", states=SIGNED_STATES)
    symp_neurological = fields.Boolean("Neurological", states=SIGNED_STATES)
    symp_arthritis = fields.Boolean("Arthralgia/Arthritis",
                                    states=SIGNED_STATES)
    symp_vomitting = fields.Boolean("Vomitting", states=SIGNED_STATES)
    symp_diarrhoea = fields.Boolean("Diarrhoea", states=SIGNED_STATES)
    recent_travel_contact = fields.Char(
        "Countries visited/Contact with traveller",
        states=SIGNED_STATES,
        help="Countries visited or from which there was contact with a "
        "traveller within the last six weeks")
    institution = fields.Many2One('gnuhealth.institution',
                                  'Institution',
                                  states={'readonly': True})
    _history = True  # enable revision control from core
    can_do_details = fields.Function(fields.Boolean('Can do triage details'),
                                     'get_do_details_perm')
    first_contact_time = fields.Function(fields.Text('First Contact Time'),
                                         'get_first_time_contact')
    done = fields.Boolean('Done', states={'invisible': True})
    end_time = fields.DateTime('End Time',
                               help='Date and time triage ended',
                               states={
                                   'readonly':
                                   Or(~Eval('can_do_details', False),
                                      Eval('done', False))
                               })
    post_appointment = fields.Many2One('gnuhealth.appointment', 'Appointment')
    # signed_by = fields.Many2One('gnuhealth.healthprofessional'', 'Signed By')
    # sign_time = fields.DateTime('Signed on')
    total_time = fields.Function(
        fields.Char('Triage Time', states={'invisible': ~Eval('done', False)}),
        'get_triage_time')

    @classmethod
    def __setup__(cls):
        super(TriageEntry, cls).__setup__()
        cls._buttons.update({
            'set_done': {
                'readonly':
                ~Eval('can_do_details', False),
                'invisible':
                Or(In(Eval('status'), ['pending', 'triage']),
                   Eval('done', False))
            },
            'go_referral': {
                'readonly': ~Eval('can_do_details', False),
                'invisible': ~In(Eval('status'), ['refer', 'referin'])
            }
        })

    @classmethod
    def _swapnote(cls, vdict):
        '''swaps out the value in the notes field for an entry that creates
        a new gnuhealth.triage.note model instance'''
        new_note = vdict.get('notes', '')
        if new_note.strip():
            new_note = new_note.strip()
            noteobj = ('create', [{'note': new_note}])
            vdict.setdefault('note_entries', []).append(noteobj)
            vdict[
                'notes'] = u''  # toDo: remove this for next release and use vdict.pop
        return vdict

    @classmethod
    def make_priority_updates(cls, triage_entries, values_to_write):
        if ('priority' in values_to_write
                and 'queue_entry' not in values_to_write):
            prio = int(values_to_write['priority'])
            queue_model = Pool().get('gnuhealth.patient.queue_entry')
            qentries = queue_model.search([('triage_entry', 'in',
                                            triage_entries)])
            values_to_write['queue_entry'] = [('write', map(int, qentries), {
                'priority': prio
            })]
        # force end-time to now if none entered and the prompt ignored
        if (values_to_write.get('done', False)
                and not values_to_write.get('end_time', False)):
            values_to_write['end_time'] = datetime.now()

        return triage_entries, cls._swapnote(values_to_write)

    @classmethod
    def create(cls, vlist):
        # add me to the queue when created
        for vdict in vlist:
            if not vdict.get('queue_entry'):
                if vdict.get('medical_alert') is True:
                    vqprio = MED_ALERT
                else:
                    try:
                        vqprio = int(vdict.get('priority', TRIAGE_MAX_PRIO))
                    except TypeError:
                        vqprio = int(TRIAGE_MAX_PRIO)

                vdict['queue_entry'] = [('create', [{
                    'busy': False,
                    'priority': vqprio
                }])]
                vdict = cls._swapnote(vdict)  # in case there's a note now
        return super(TriageEntry, cls).create(vlist)

    @classmethod
    def write(cls, records, values, *args):
        # update queue priority when mine updated
        # but only if it's higher or there's no appointment
        records, values = cls.make_priority_updates(records, values)
        newargs = []
        if args:
            arglist = iter(args)
            for r, v in zip(arglist, arglist):
                r, v = cls.make_priority_updates(r, v)
                newargs.extend([r, v])
        return super(TriageEntry, cls).write(records, values, *newargs)

    @staticmethod
    def default_priority():
        return str(TRIAGE_MAX_PRIO)

    @staticmethod
    def default_status():
        return 'pending'

    def get_triage_time(self, name):
        endtime = self.end_time if self.done else datetime.now()
        return get_elapsed_time(self.create_date, endtime)

    def get_name(self, name):
        if name == 'name':
            if self.patient:
                return self.patient.name.name
            else:
                return '%s, %s' % (self.lastname, self.firstname)
        return ''

    @classmethod
    def search_name(cls, name, clause):
        fld, operator, operand = clause
        return [
            'OR', ('patient.name.name', operator, operand),
            ('firstname', operator, operand), ('lastname', operator, operand)
        ]

    @classmethod
    def search_id(cls, name, clause):
        fld, operator, operand = clause
        return [
            'OR' ('patient.name.upi', operator, operand),
            ('patient.medical_record_num', operator, operand),
            ('id_number', operator, operand)
        ]

    @classmethod
    def get_patient_party_field(cls, instances, name):
        out = dict([(i.id, '') for i in instances])
        if name == 'upi':
            out.update([(i.id, i.patient.puid) for i in instances
                        if i.patient])
        return out

    def get_id_display(self, name):
        idtypedict = dict(ID_TYPES)
        if self.patient:
            return '{} / {}'.format(self.patient.puid,
                                    self.patient.medical_record_num)
        elif self.id_number and self.id_type:
            return ': '.join(
                [idtypedict.get(self.id_type, '??'), self.id_number])
        else:
            return ''

    def patient_search_result(self, name):
        # ToDo: perform search against patient/party and return
        # the ones that match.
        # the domain should include :
        # lastname, firstname, sex, id_type, id_number
        return []

    def get_status_display(self, name):
        return TRIAGE_STATUS_LOOKUP.get(self.status)

    def get_childbearing_age(self, name):
        if self.patient:
            return self.patient.childbearing_age
        elif self.sex == 'm':
            return False
        else:
            age = self.age
            if age.isdigit():
                age = int(age)
            elif age[:-1].isdigit():
                age = int(age[:-1])
            else:
                age = 7  # hack to make a default false for BHC
            if age < MENARCH[0] or age > MENARCH[1]:
                return False
        return True

    def get_sex_age_display(self, name):
        field = name[:3]
        if self.patient:
            return getattr(self.patient, field)
        else:
            return getattr(self, field)

    @fields.depends('sex', 'patient')
    def on_change_with_childbearing_age(self, *a, **k):
        if self.patient:
            return self.patient.childbearing_age
        else:
            if self.sex == 'm':
                return False
            else:
                return True

    @classmethod
    def get_medical_alert(cls, instances, name):
        out = [(i.id, i.priority == MED_ALERT) for i in instances]
        return dict(out)

    @classmethod
    def set_medical_alert(cls, instances, name, value):
        to_write = []
        if value is False:
            return
        for i in instances:
            if i.priority > MED_ALERT:
                to_write.append(i)
        cls.write(to_write, {'priority': MED_ALERT})

    @classmethod
    def get_do_details_perm(cls, instances, name):
        user_has_perm = get_model_field_perm(cls.__name__,
                                             name,
                                             'create',
                                             default_deny=False)
        outval = dict([(x.id, user_has_perm) for x in instances])
        return outval

    @staticmethod
    def default_can_do_details():
        user_has_perm = get_model_field_perm('gnuhealth.triage.entry',
                                             'can_do_details',
                                             'create',
                                             default_deny=False)
        return user_has_perm

    @staticmethod
    def default_childbearing_age():
        return True

    @staticmethod
    def uri_selection():
        return [(None, '')] + URINALYSIS['default']

    @staticmethod
    def uri_nitrite_selection():
        return [(None, '')] + URINALYSIS['nitrite']

    def get_first_time_contact(self, name):
        '''This method gets the date and time 
           this person was first made contact
           with by the attending staff'''
        return localtime(self.create_date).strftime('%F %T')

    @staticmethod
    def default_institution():
        HI = Pool().get('gnuhealth.institution')
        return HI.get_institution()

    def get_note_display(self, name):
        notes = []
        if self.note_entries:
            return u'\n---\n'.join(
                map(lambda x: u' :\n'.join([x.byline, x.note]),
                    self.note_entries))
        else:
            return ''

    @classmethod
    @ModelView.button_action('health_triage_queue.act_triage_referral_starter')
    def go_referral(cls, queue_entries):
        pass

    @classmethod
    @ModelView.button
    def set_done(cls, entries):
        '''set done=True on the triage entry'''
        save_data = {'done': True}
        for entry in entries:
            if not entry.end_time:
                cls.raise_user_warning(
                    'triage_end_date_warn1',
                    'End time has not been set.\nThe current Date and time '
                    'will be used.')
                save_data.update(end_time=datetime.now())
        cls.write(entries, save_data)
Пример #29
0
class Mandate(Workflow, ModelSQL, ModelView):
    'SEPA Mandate'
    __name__ = 'account.payment.sepa.mandate'
    party = fields.Many2One('party.party',
                            'Party',
                            required=True,
                            select=True,
                            states={
                                'readonly':
                                Eval('state').in_(
                                    ['requested', 'validated', 'canceled']),
                            },
                            depends=['state'])
    account_number = fields.Many2One(
        'bank.account.number',
        'Account Number',
        ondelete='RESTRICT',
        states={
            'readonly': Eval('state').in_(['validated', 'canceled']),
            'required': Eval('state') == 'validated',
        },
        domain=[
            ('type', '=', 'iban'),
            ('account.owners', '=', Eval('party')),
        ],
        depends=['state', 'party'])
    identification = fields.Char('Identification',
                                 size=35,
                                 states={
                                     'readonly':
                                     Eval('identification_readonly', True),
                                     'required':
                                     Eval('state') == 'validated',
                                 },
                                 depends=['state', 'identification_readonly'])
    identification_readonly = fields.Function(
        fields.Boolean('Identification Readonly'),
        'get_identification_readonly')
    company = fields.Many2One(
        'company.company',
        'Company',
        required=True,
        select=True,
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=',
                      '!='), Eval('context', {}).get('company', -1)),
        ],
        states={
            'readonly': Eval('state') != 'draft',
        },
        depends=['state'])
    type = fields.Selection([
        ('recurrent', 'Recurrent'),
        ('one-off', 'One-off'),
    ],
                            'Type',
                            states={
                                'readonly':
                                Eval('state').in_(['validated', 'canceled']),
                            },
                            depends=['state'])
    sequence_type_rcur = fields.Boolean("Always use RCUR",
                                        states={
                                            'invisible':
                                            Eval('type') == 'one-off',
                                        },
                                        depends=['type'])
    scheme = fields.Selection([
        ('CORE', 'Core'),
        ('B2B', 'Business to Business'),
    ],
                              'Scheme',
                              required=True,
                              states={
                                  'readonly':
                                  Eval('state').in_(['validated', 'canceled']),
                              },
                              depends=['state'])
    scheme_string = scheme.translated('scheme')
    signature_date = fields.Date('Signature Date',
                                 states={
                                     'readonly':
                                     Eval('state').in_(
                                         ['validated', 'canceled']),
                                     'required':
                                     Eval('state') == 'validated',
                                 },
                                 depends=['state'])
    state = fields.Selection([
        ('draft', 'Draft'),
        ('requested', 'Requested'),
        ('validated', 'Validated'),
        ('canceled', 'Canceled'),
    ],
                             'State',
                             readonly=True)
    payments = fields.One2Many('account.payment', 'sepa_mandate', 'Payments')
    has_payments = fields.Function(fields.Boolean('Has Payments'),
                                   'has_payments')

    @classmethod
    def __setup__(cls):
        super(Mandate, cls).__setup__()
        cls._transitions |= set((
            ('draft', 'requested'),
            ('requested', 'validated'),
            ('validated', 'canceled'),
            ('requested', 'canceled'),
            ('requested', 'draft'),
        ))
        cls._buttons.update({
            'cancel': {
                'invisible': ~Eval('state').in_(['requested', 'validated']),
                'depends': ['state'],
            },
            'draft': {
                'invisible': Eval('state') != 'requested',
                'depends': ['state'],
            },
            'request': {
                'invisible': Eval('state') != 'draft',
                'depends': ['state'],
            },
            'validate_mandate': {
                'invisible': Eval('state') != 'requested',
                'depends': ['state'],
            },
        })
        # t = cls.__table__()
        # JMO/RSE 2017_04_18 Following 4929e02594d
        # We override in coog the possibility to keep the mandate
        # for several bank account but we suffer from the register order
        # if we try to delete the constraint from coog module
        # t = cls.__table__()
        # cls._sql_constraints = [
        #     ('identification_unique', Unique(t, t.company, t.identification),
        #         'account_payment_sepa.msg_mandate_unique_id'),
        #     ]

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

    @staticmethod
    def default_type():
        return 'recurrent'

    @classmethod
    def default_sequence_type_rcur(cls):
        return False

    @staticmethod
    def default_scheme():
        return 'CORE'

    @staticmethod
    def default_state():
        return 'draft'

    @staticmethod
    def default_identification_readonly():
        pool = Pool()
        Configuration = pool.get('account.configuration')
        config = Configuration(1)
        return bool(config.sepa_mandate_sequence)

    def get_identification_readonly(self, name):
        return bool(self.identification)

    def get_rec_name(self, name):
        if self.identification:
            return self.identification
        return '(%s)' % self.id

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

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

        config = Configuration(1)
        vlist = [v.copy() for v in vlist]
        for values in vlist:
            if (config.sepa_mandate_sequence
                    and not values.get('identification')):
                values['identification'] = Sequence.get_id(
                    config.sepa_mandate_sequence.id)
            # Prevent raising false unique constraint
            if values.get('identification') == '':
                values['identification'] = None
        return super(Mandate, cls).create(vlist)

    @classmethod
    def write(cls, *args):
        actions = iter(args)
        args = []
        for mandates, values in zip(actions, actions):
            # Prevent raising false unique constraint
            if values.get('identification') == '':
                values = values.copy()
                values['identification'] = None
            args.extend((mandates, values))
        super(Mandate, cls).write(*args)

    @classmethod
    def copy(cls, mandates, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('payments', [])
        default.setdefault('signature_date', None)
        default.setdefault('identification', None)
        return super(Mandate, cls).copy(mandates, default=default)

    @property
    def is_valid(self):
        if self.state == 'validated':
            if self.type == 'one-off':
                if not self.has_payments:
                    return True
            else:
                return True
        return False

    @property
    def sequence_type(self):
        if self.type == 'one-off':
            return 'OOFF'
        elif not self.sequence_type_rcur and (not self.payments or all(
                not p.sepa_mandate_sequence_type
                for p in self.payments) or all(p.rejected
                                               for p in self.payments)):
            return 'FRST'
        # TODO manage FNAL
        else:
            return 'RCUR'

    @classmethod
    def has_payments(cls, mandates, name):
        pool = Pool()
        Payment = pool.get('account.payment')
        payment = Payment.__table__
        cursor = Transaction().connection.cursor()

        has_payments = dict.fromkeys([m.id for m in mandates], False)
        for sub_ids in grouped_slice(mandates):
            red_sql = reduce_ids(payment.sepa_mandate, sub_ids)
            cursor.execute(*payment.select(payment.sepa_mandate,
                                           Literal(True),
                                           where=red_sql,
                                           group_by=payment.sepa_mandate))
            has_payments.update(cursor.fetchall())

        return {'has_payments': has_payments}

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

    @classmethod
    @ModelView.button
    @Workflow.transition('requested')
    def request(cls, mandates):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('validated')
    def validate_mandate(cls, mandates):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('canceled')
    def cancel(cls, mandates):
        # TODO must be automaticaly canceled 13 months after last collection
        pass

    @classmethod
    def delete(cls, mandates):
        for mandate in mandates:
            if mandate.state not in ('draft', 'canceled'):
                raise AccessError(
                    gettext(
                        'account_payment_sepa'
                        '.msg_mandate_delete_draft_canceled',
                        mandate=mandate.rec_name))
        super(Mandate, cls).delete(mandates)
Пример #30
0
class Template:
    __metaclass__ = PoolMeta
    __name__ = 'product.template'
    customs_category = fields.Many2One('product.category',
                                       'Customs Category',
                                       domain=[
                                           ('customs', '=', True),
                                       ],
                                       states={
                                           'required':
                                           Eval('tariff_codes_category',
                                                False),
                                       },
                                       depends=['tariff_codes_category'])
    tariff_codes_category = fields.Boolean(
        "Use Category's Tariff Codes",
        help='Use the tariff codes defined on the category')
    tariff_codes = fields.One2Many(
        'product-customs.tariff.code',
        'product',
        'Tariff Codes',
        order=[('sequence', 'ASC'), ('id', 'ASC')],
        states={
            'invisible': ((Eval('type') == 'service')
                          | Eval('tariff_codes_category', False)),
        },
        depends=['type', 'tariff_codes_category'])

    @classmethod
    def __register__(cls, module_name):
        TableHandler = backend.get('TableHandler')
        cursor = Transaction().connection.cursor()
        pool = Pool()
        Category = pool.get('product.category')
        sql_table = cls.__table__()
        category = Category.__table__()

        table = TableHandler(cls, module_name)
        category_exists = table.column_exist('category')

        super(Template, cls).__register__(module_name)

        # Migration from 3.8: duplicate category into account_category
        if category_exists:
            # Only accounting category until now
            cursor.execute(*category.update([category.customs], [True]))
            cursor.execute(*sql_table.update([sql_table.customs_category],
                                             [sql_table.category]))

    @classmethod
    def default_tariff_codes_category(cls):
        return False

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

    @classmethod
    def view_attributes(cls):
        return super(Template, cls).view_attributes() + [
            ('//page[@id="customs"]', 'states', {
                'invisible': Eval('type') == 'service',
            }),
        ]

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

        super(Template, cls).delete(templates)

        for products in grouped_slice(products):
            product_tariffcodes = Product_TariffCode.search([
                'product',
                'in',
                list(products),
            ])
            Product_TariffCode.delete(product_tariffcodes)