コード例 #1
0
class Adjustment(models.TransientModel):
    _name = 'xopgi.unrealized_gl_adjustment'

    wizard = fields.Many2one('xopgi.unrealized_gl_wizard')
    account = fields.Many2one('account.account')

    account_name = fields.Char(related="account.name")
    account_code = fields.Char(related="account.code")
    account_currency = fields.Many2one(related="account.currency_id")

    foreign_balance = fields.Float(compute='_compute_all', default=0)
    balance = fields.Float(compute='_compute_all', default=0)
    adjusted_balance = fields.Float(compute='_compute_all', default=0)
    gainloss = fields.Float(compute='_compute_all', default=0)

    @api.depends('account', 'wizard')
    def _compute_all(self):
        precision = DecimalPrecision.precision_get('Account')
        company_currency = self.env.user.company_id.currency_id
        # Map records to accounts so that we can compute the balances in a single
        # DB query
        account_map = dict(zip(self.mapped('account.id'), self))
        assert len(account_map) == len(self)
        close_date = self[0].wizard.close_date
        tables, where_clause, where_params = AccountMoveLine.with_context(
            state='posted', date_to=close_date)._query_get()
        if not tables:
            tables = '"account_move_line"'
        if where_clause.strip():
            filters = [where_clause]
        else:
            filters = []
        filters.append('"account_move_line"."account_id" IN %s')
        where_params.append(tuple(account_map.keys()))
        query = ('''
            SELECT account_id AS id,
                   COALESCE(SUM(debit), 0) - COALESCE(SUM(credit), 0) AS balance,
                   COALESCE(SUM(amount_currency), 0) as foreign_balance
               FROM {tables}
               WHERE {filters}
               GROUP BY account_id
        ''').format(tables=tables, filters=' AND '.join(filters))
        self.env.cr.execute(query, where_params)
        for row in self.env.cr.dictfetchall():
            record = account_map.pop(int(
                row['id']))  # cast to int, otherwise KeyError
            account = record.account
            record.balance = balance = row['balance']
            record.foreign_balance = row['foreign_balance']
            record.adjusted_balance = adjusted = account.currency_id.with_context(
                date=close_date).compute(
                    record.foreign_balance,
                    company_currency,
                    round=False,
                )
            record.gainloss = round(adjusted - balance, precision)
        for record in account_map.values():
            record.balance = record.foreign_balance = 0
            record.adjusted_balance = record.gainloss = 0
コード例 #2
0
ファイル: system_event.py プロジェクト: merchise/xopgi.base
class RecurrentEvent(models.Model):
    _name = 'cdr.recurrent.event'
    _description = "Recurrent CDR event"

    _inherits = {
        'cdr.system.event': 'event_id',
        'cdr.recurrent.event.def': 'recurrence_def'
    }

    event_id = fields.Many2one('cdr.system.event',
                               required=True,
                               ondelete='cascade')

    time = fields.Float()

    recurrence_def = fields.Many2one('cdr.recurrent.event.def',
                                     required=True,
                                     ondelete='cascade')

    def update_event(self, value):
        '''Update the fields next call, state and action for an event.
        When an event is evaluated is necessary to update its values.

        '''
        next_call = self.recurrence_def.next_date(self.recurrence_def.rrule)
        state = 'raising' if value else 'not_raising'
        action = 'raise' if state == 'raising' else 'do_nothing'
        values = dict(next_call=next_call, state=state, action=action)
        self.write(values)

    def evaluate(self, cycle):
        '''Evaluate the recurrent event in a evaluation cycle.

        '''
        if isinstance(cycle, int):
            cycle = self.env['cdr.evaluation.cycle'].browse(cycle)
        try:
            value = self.event_id._evaluate()
        except Exception:
            logger.exception('Error evaluating event %s defined as: ',
                             self.name, self.definition)
            return None
        else:
            self.update_event(value)

    @api.model
    @api.returns('self', lambda value: value.id)
    def create(self, vals):
        if any(field in self.recurrence_def._fields for field in vals):
            vals.update(is_recurrent=True)
        return super(RecurrentEvent, self).create(vals)
コード例 #3
0
ファイル: models.py プロジェクト: merchise/xopgi.base
class A(models.Model):
    _name = 'model.a'

    add_char = fields.Char()
    add_text = fields.Text()
    price_int = fields.Integer()
    cost_float = fields.Float()
    min_int = fields.Integer()
    max_int = fields.Integer()
    active = fields.Boolean(default=True)
    parent_id = fields.Many2one('model.a', 'Parent')
    meldb_ids = fields.Many2many(comodel_name='model.b',
                                 column1='meld_column2_id',
                                 column2='meld_column1_id',
                                 string='relation')
コード例 #4
0
ファイル: system_event.py プロジェクト: merchise/xopgi.base
class BasicEvent(models.Model):
    _name = 'cdr.basic.event'
    _description = 'Basic CDR event'

    _inherits = {'cdr.system.event': 'event_id'}

    event_id = fields.Many2one('cdr.system.event',
                               required=True,
                               ondelete='cascade')

    interval = fields.Float(
        required=True,
        help='Time (in hours:minutes format) between evaluations.')

    time_to_wait = fields.Float(
        required=True,
        help='Time (in hours:minutes format) getting '
        'consecutive positive evaluations before raise.')

    times_to_raise = fields.Integer(
        help='Waiting time to launch an event while an evidence is true in a '
        'time interval')

    @api.depends('interval')
    def get_next_call(self):
        '''Compute the next evaluation date.

        '''
        for event in self:
            if event.active and event.interval:
                event.next_call = datetime.now() + timedelta(
                    hours=event.interval)
            else:
                event.next_call = False

    def update_event(self, value, cycle):
        '''Update the fields next call, state and action for an event.
        When an event is evaluated is necessary to update its values.

        '''
        next_call = str2dt(cycle.create_date) + timedelta(hours=self.interval)
        # If the interval is less or equal zero that means that the event does
        # not wait any time to launch.
        if self.interval <= 0:
            times_to_raise = -1
        else:
            times_to_raise = ((self.time_to_wait / self.interval)
                              if not value else self.times_to_raise - 1)
        state = 'raising' if value and times_to_raise < 1 else 'not_raising'
        if self.state == 'raising':
            action = 'continue_raising' if state == 'raising' else 'stop_raising'
        else:
            action = 'raise' if state == 'raising' else 'do_nothing'
        values = dict(next_call=next_call, state=state, action=action)
        self.write(values)

    def evaluate(self, cycle):
        '''Evaluate the basic event in a evaluation cycle.

        '''
        if isinstance(cycle, int):
            cycle = self.env['cdr.evaluation.cycle'].browse(cycle)
        try:
            value = self.event_id._evaluate()
        except Exception:
            logger.exception('Error evaluating event %s defined as: ',
                             self.name, self.definition)
            return None
        else:
            self.update_event(value, cycle)
コード例 #5
0
class AccountAnalyticAccount(models.Model):
    _inherit = 'account.analytic.account'

    # The formulae for the commission to salesmen is based on a simple
    # projection from the desired sales margin (required_margin, max_margin)
    # to the commission margins salesmen earn per sale.
    #
    # The projection is simply a geometrical projection: required_margin
    # yields the minimal commission margin, the max_margin yields the maximal
    # commission margin.  Accounts that fail to achieve the required_margin
    # yield 0% commission, accounts that surpass the max_margin yields just
    # the maximal commission margin.
    #
    # The projection follows the scheme below:
    #
    #     min margin ----------X------------------ max margin
    #                          |
    #     min com    ----------V------------------ max comm
    #
    # X represents the actual margin, V represents the resultant commission
    # margin.
    #
    # V = min_comm * alpha + max_comm * (1 - alpha), where alpha is given by
    # the formula:
    #
    # alpha = (max_margin - X)/(max_margin - min_margin)
    #
    # if alpha < 0: alpha = 0   # Too much margin
    #
    # if V < min_comm: V = 0
    #
    # Each account may have any desired required_margin and max_margin,
    # provided the required_margin is greater or equal of is parent account
    # and the max_margin is less or equal than the parent account.
    #
    # The account may have any desired min commission and max commission, no
    # restrictions are placed.
    #
    # By default, accounts take all these parameters from its parent. So it's
    # wise to only establish them at accounts that consolidate operational
    # branches.
    #

    required_margin = fields.Float(
        string='Required margin',
        help=('This is the minimum margin required for an operation. If 0 '
              'no commission will be calculated.'),
        required=False,
        default=0,
        track_visibility='onchange',
        digits=dp.get_precision('Account'),
    )
    max_margin = fields.Float(
        string='Maximum margin',
        help='This is maximum margin allowed for an operation.',
        required=False,
        default=0,
        track_visibility='onchange',
        digits=dp.get_precision('Account'),
    )
    min_commission_margin = fields.Float(
        string='Minimum commission margin',
        help='This is minimum margin for commissions.',
        required=False,
        default=0,
        track_visibility='onchange',
        digits=dp.get_precision('Account'),
    )
    max_commission_margin = fields.Float(
        string='Maximum commission margin',
        help='This is maximum margin for commissions.',
        required=False,
        default=0,
        track_visibility='onchange',
        digits=dp.get_precision('Account'),
    )

    current_required_margin = fields.Float(
        compute=_compute_from_branch('required_margin',
                                     'current_required_margin', default=0),
        digits=dp.get_precision('Account'),
    )
    current_max_margin = fields.Float(
        compute=_compute_from_branch('max_margin',
                                     'current_max_margin', default=0),
        digits=dp.get_precision('Account'),
    )
    current_min_comm = fields.Float(
        compute=_compute_from_branch('min_commission_margin',
                                     'current_min_comm', default=0),
        digits=dp.get_precision('Account'),
    )
    current_max_comm = fields.Float(
        compute=_compute_from_branch('max_commission_margin',
                                     'current_max_comm', default=0),
        digits=dp.get_precision('Account'),
    )

    percentage_margin = fields.Float(
        string='Margin %', help='Percentage margin related to credit.',
        compute='_compute_commission',
        digits=dp.get_precision('Account'),
    )

    percentage_commission = fields.Float(
        string='Commission %',
        help='Percentage commission related to profit.',
        compute='_compute_commission',
        digits=dp.get_precision('Account'),
    )

    commission = fields.Float(
        string='Commission', help='Commission related to profit.',
        compute='_compute_commission',
        digits=dp.get_precision('Account'),
    )

    invoiced = fields.Float(
        string='Invoiced',
        help=('The amount invoiced for this account, '
              'discounting refunds.'),
        compute='_compute_invoiced',
        digits=dp.get_precision('Account'),
        store=True,
        compute_sudo=True
    )

    expended = fields.Float(
        string='Expended',
        help=('The amount expended for this account, '
              'discounting refunds.'),
        compute='_compute_invoiced',
        digits=dp.get_precision('Account'),
        store=True,
        compute_sudo=True
    )

    amount_undefined = fields.Float(
        string='Undefined',
        help=('Total that cannot be accounted as invoiced or expended because '
              'it is not attached to an invoice'),
        compute='_compute_invoiced',
        digits=dp.get_precision('Account'),
        store=True,
        compute_sudo=True
    )

    self_balance = fields.Float(
        string='Balance',
        help=('Self balance'),
        compute='_compute_invoiced',
        digits=dp.get_precision('Account'),
        store=True,
        compute_sudo=True
    )

    primary_salesperson_id = fields.Many2one(
        "res.users", string="Salesperson",
        help="Primary salesperson in operation",
        compute="_compute_primary_salesperson",
        store=True
    )

    supplier_invoice_id = fields.Many2one('account.invoice',
                                          ondelete='set null')

    # TODO: Ensure only groups='base.group_sale_manager' can update the
    # commission margins.  So far, only the goodwill of ignorance may save us.
    @api.depends('line_ids')
    def _compute_invoiced(self):
        for record in self:
            invoiced = expended = undefined = 0
            for line in record.line_ids:
                if line.move_id and line.move_id.invoice_id:
                    if line.move_id.invoice_id.type in INCOME_INVOICE_TYPES:
                        invoiced += line.amount
                    else:
                        expended -= line.amount
                else:
                    undefined += line.amount
            record.invoiced = invoiced
            record.expended = expended
            record.amount_undefined = undefined
            record.self_balance = invoiced - expended + undefined

    @api.depends('invoiced', 'self_balance')
    def _compute_commission(self):
        for record in self:
            margin, comm_percent, balance = _compute_margin_commission(record)
            record.percentage_margin = margin * 100
            record.percentage_commission = comm_percent * 100
            record.commission = comm_percent * balance

    @api.depends('line_ids.move_id.invoice_id.user_id')
    def _compute_primary_salesperson(self):
        for account in self:
            if not account.active:
                account.primary_salesperson_id = False
            else:
                user_id = next(
                    (line.move_id.invoice_id.user_id
                     for line in account.line_ids
                     if line and line.move_id and line.move_id.invoice_id
                     if line.move_id.invoice_id.type in INCOME_INVOICE_TYPES
                     if line.move_id.invoice_id.user_id),
                    None
                )
                if user_id:
                    account.primary_salesperson_id = user_id
                else:
                    account.primary_salesperson_id = False

    @api.constrains('required_margin', 'max_margin')
    def _validate_margins(self):
        for record in self:
            required_margin = record.required_margin
            max_margin = record.max_margin
            if bool(required_margin) != bool(max_margin):
                # Either both are set or both are unset
                raise ValidationError('You must set both required_margin '
                                      'and max_margin or none.')
            if required_margin < 0 or required_margin > 100:
                raise_validation_error('required minimum margin')
            if max_margin < 0 or max_margin > 100:
                raise_validation_error('maximum allowed margin')
            if required_margin and max_margin and \
               required_margin >= max_margin:
                raise ValidationError('required margin must be less that '
                                      'max margin.')
            parent = record.parent_id
            if parent and parent.id:
                if parent.required_margin and required_margin and \
                   required_margin < parent.required_margin:
                    raise ValidationError(
                        'You cannot lower the required margin')
                if parent.max_margin and max_margin and \
                   max_margin > parent.max_margin:
                    raise ValidationError(
                        'You cannot raise the maximum margin')
            # TODO: If the children enter in violation what to do?
            # for child in record.complete_child_ids:
            #     child._validate_margins()

    @api.constrains('min_commission_margin', 'max_commission_margin')
    def _validate_commission_margins(self):
        for record in self:
            min_comm = record.min_commission_margin
            max_comm = record.max_commission_margin
            if bool(min_comm) != bool(max_comm):
                # Either both are set or both are unset
                    raise ValidationError('You must set both min commission '
                                          'and max commission or none.')
            if min_comm < 0 or min_comm > 100:
                raise_validation_error('minimum commission margin')
            if max_comm < 0 or max_comm > 100:
                raise_validation_error('maximum commission margin')
            if min_comm and max_comm and min_comm >= max_comm:
                raise ValidationError('min commission must be less that '
                                      'max commission.')
コード例 #6
0
class RecurrentModel(models.AbstractModel):
    '''A mixin for recurrent things (in time).

    This mixin modifies `search` so that your model can be seen as an
    occurrence of the recurrent event.  You must provide `search_occurrences`
    (or the deprecated `virtual_id`) context key to True to trigger this
    behavior.

    The actual recurrence model is that implemented by the python module
    `dateutil.rrule`:mod:.

    Examples::

      class MyMeeting(Model):
          _name = 'my.meeting'
          _inherit = [_name, 'recurrent.model']

      >>> Meeting = self.env['my.meeting']

      # Case 1:Repeat once every day

      >>> meeting = Meeting.create(dict(
      ...    interval=1,
      ...    freq='daily',
      ...    end_type='no_end_date'
      ... ))

      # Case 2:Repeat each week on Mondays and Fridays

      >>> meeting = Meeting.create(dict(
      ...    interval=1,
      ...    freq='weekly',
      ...    mo=True,
      ...    fr=True,
      ...    end_type='no_end_date'
      ... ))

      # Case 3:Repeat each month the nth days Mondays and Fridays':

      >>> meeting = Meeting.create(dict(
      ...    interval=1,
      ...    freq='monthly',
      ...    days_option='week_day',
      ...    mo=True,
      ...    fr=True,
      ...    end_type='no_end_date'
      ... ))


      # Case 4:Repeat each month the cardinal days of month:

      >>> meeting = Meeting.create(dict(
      ...    interval=1,
      ...    freq='monthly',
      ...    days_option='month_day',
      ...    monthly_day=5,
      ...    end_type='no_end_date',
      ...    end_type='no_end_date'
      ... ))

      # Repeat each year, similar to case three specifying that month of the
      # year.

      >>> meeting = Meeting.create(dict(
      ...    interval=1,
      ...    freq='yearly',
      ...    days_option='month_day',
      ...    monthly_day=5,
      ...    months=1,
      ...    end_type='no_end_date'
      ... ))

      # Repeat each year specifying number of days before western easter
      # sunday.

      >>> meeting = Meeting.create(dict(
      ...    interval=1,
      ...    freq='yearly',
      ...    is_easterly=True,
      ...    byeaster_day=-2,
      ...    end_type='no_end_date'
      ... ))

    :attr months: specify year month.
    :attr monthly_day: Refers to the nth day of the month.
    :attr byeaster: Number of days from western easter sunday by default is 0.

    '''
    _name = RECURRENT_MIXIN_MODEL
    _inherit = [RECURRENT_DESCRIPTION_MODEL]
    _description = 'Recurrent model'

    date_to = fields.Date(
        'Final date',
        help='End date of the occurrence'
    )

    @api.multi
    def _get_date(self):
        '''Get the values for the compute 'date' field of the recurrent object
        using the user timezone.  This is calculated with the field 'date_from'.

        '''
        for item in self:
            # Plus an hour to avoid the problem with daylight saving time.
            date_from = normalize_dt(item.date_from) + timedelta(hours=1)
            if isinstance(item.id, string_types):
                start, _ = self._extract_dates(item.id,
                                               item.calendar_duration or 23)
                item.date = normalize_dt_str(start)
            else:
                item.date = normalize_dt_str(date_from)

    def _search_date(self, operator, value):
        """Get the domain to search for 'date'.

        We simply translate it to search by 'date_from'.

        """
        if value not in (False, None):
            # Some modules try to see if the date is not set by passing False.
            value = normalize_date(value)
        return [('date_from', operator, value)]

    @api.multi
    def _get_duration(self):
        '''Get the values on hours to the compute field 'calendar_duration'  of
        recurrent object.

        '''
        for item in self:
            # By default 'duration = 1 day'
            if item.duration:
                # Minus 2 to avoid problems with daylight savings. This allows
                # to have instances from 1AM to 11PM in case change of use
                # schedule is still on the same day.
                item.calendar_duration = item.duration * _HOURS_PER_DAY - 2
            elif item.date_to:
                df = normalize_date(item.date_from)
                dt = normalize_date(item.date_to)
                item.calendar_duration = (dt - df).total_seconds() / _SECS_PER_HOUR
            else:
                item.calendar_duration = 1

    date = fields.Datetime(
        compute=_get_date,
        search=_search_date,
        method=True,
        string='Date and time for representation in calendar views',
    )

    calendar_duration = fields.Float(
        compute='_get_duration',
        method=True,
        string='Duration',
        help='Get the duration on hours of the recurrence'
    )

    active = fields.Boolean(
        'Active',
        default=True,
        help='Indicate if recurrent object is active or not'
    )

    is_recurrent = fields.Boolean(
        'Is recurrent',
        default=True,
        help='Check if is recurrent.'
    )

    @api.model
    @api.returns('self', downgrade=_SEARCH_DOWNGRADE)
    def search(self, args, offset=0, limit=None, order=None, count=False):
        """If 'virtual_id' not in context or are False then normally
        search, else search virtual occurrences and return then.

        """
        virtual_id = self._context.get('virtual_id', False)
        if virtual_id and not count:
            return self._logical_search(args, offset=offset, limit=limit, order=order)
        else:
            return self._real_search(args, offset=offset, limit=limit, order=order, count=count)

    @api.model
    def _real_search(self, args, offset=0, limit=None, order=None, count=False):
        return super(RecurrentModel, self).search(
            args,
            offset=offset,
            limit=limit,
            order=order,
            count=count
        )

    @api.model
    def _logical_search(self, domain, offset=0, limit=None, order=None):
        '''Finds all occurrences that match the domain.

        Since there can be an unlimited number of occurrences, `limit` is
        always bounded to MAX_OCCURRENCES.

        '''
        import operator
        from xoutil.future.functools import reduce
        from xoutil.future.itertools import ifilter, first_n, map
        from xoutil.eight import integer_types
        from xoutil.fp.tools import compose
        from ..tools.predicate import Predicate

        # Normalize limit to be 0 <= limit <= MAX_OCCURRENCES.  If limit is
        # zero, bail the whole procedure by returning the empty recordset
        limit = min(max(limit or 0, 0), MAX_OCCURRENCES)
        if not limit:
            return self.browse()

        # This is a tricky implementation to get right.  Domain can be
        # arbitrarily complex, and mix date-related and non-date related
        # conditions.  Since all non-date related conditions will match for
        # both the occurrences and the recurrent pattern itself, we'd like to
        # find the *recurrence patterns* that match the domain **striping the
        # date-related conditions**, and then rematch the occurrences with the
        # rest of the date-related domain.
        #
        # This is to say that if we're given a domain T that may contain
        # date-related conditions, we would like to find another domain Q
        # without date-related conditions such that ``T => Q``.
        #
        # Then we could find recurrence patterns that match Q, and afterwards
        # filter occurrences by T.  This would make the amount of filtering
        # done in Python less of an issue.
        #
        # Trivially, for any T, making ``Q=[]`` (i.e always True) satisfies
        # this condition, but then will have to filter all of the recurrence
        # in Python and that might be a performance issue.
        #
        # Yet, trying to find a non-trivial Q cannot be achieved in all cases.
        #
        # In the following, all capitalized symbols starting with D will
        # represent predicates (terms in a domain) involving the `date` field,
        # non capitalized symbols will represent predicates not involving the
        # `date` field.  The logical AND and OR will be represented by `and`
        # and `or`, where the logical NOT will be `~`.
        #
        # For the predicate T ``(D or ~n) and (~D or n)``, Q can be ``~n or
        # n``, which is the same as ``[]``.  For the predicate ``(D or n) and
        # (~D or n)``, we can find Q as ``n``.  Notice that both predicates
        # are in Conjunctive Normal Form (CNF).
        #
        # For Odoo domains, we can use the 2nd normal form (2NF) which
        # distributes NOT within the term themselves, this mean we won't see
        # any ``~`` in our predicates.  So a CNF can be ``(D or nn) and (DD or
        # n)`` that will yield Q to be ``~nn or ~n``, which *we* (but not our
        # machinery) know to be equivalent to ``[]``.
        #
        # If the domain is given in CNF and 2NF, it's easy to find Q. So, the
        # algorithm needed is to find the CNF for the 2NF of a domain.
        #
        # HOWEVER, I believe that this is NOT currently needed IN OUR CASE.
        # So let's mark it as a thing to do.
        #
        # TODO:  Compute the CNF to get Q.
        #
        res = self._real_search([], order=order)
        # At this point `res` has the recurrence in the order requested.
        # _iter_occurrence will yield the occurrences in the `date` order
        # first.
        assert all(isinstance(r.id, integer_types) for r in res)
        pred = Predicate(domain)
        occurrences = first_n(ifilter(pred, map(
            compose(self.browse, self._real_id2virtual),
            res._iter_occurrences_dates()
        )), limit)
        return reduce(operator.or_, occurrences, self.browse())

    @api.multi
    def read(self, fields=None, load='_classic_read'):
        '''Return the list of dict with the [fields].

        If [ids] contain virtual ids read data from real ids and create
        virtual copies to return

        If `fields` have [date, date_from, date_to] return a list of dict with
        this fields plus id. If :param calendar_duration: is include in fields
        it will be add to the result else is exclude.

        '''
        if not fields:
            fields = []
        fields2 = fields[:] if fields else []
        targets = ('date', 'date_from', 'date_to')
        has_dates = any(field in fields for field in targets)
        if fields and 'calendar_duration' not in fields and has_dates:
            fields2.append('calendar_duration')
        ids_index = {record.id: int(self._virtual_id2real(record.id))
                     for record in self}
        # If dict:ids_index have as keys virtual_ids:(str,unicode) the
        # id_reals are dict.values() else the id_reals are dict.keys()
        ids_reals = list(set(ids_index.values()))
        reals = self.browse(ids_reals)
        values = super(RecurrentModel, reals).read(fields2, load=load)
        results = {r['id']: r for r in values}

        def select(virtual_id, data):
            if data is None:
                return None
            else:
                if isinstance(virtual_id, string_types):
                    date_show, _ = self._extract_dates(virtual_id, 23)
                    data['id'] = virtual_id
                    data['date'] = date_show
                return data

        return list(filter(bool, (
            select(key, results.get(ids_index[key], None))
            for key in ids_index
        )))

    @api.multi
    def unlink(self):
        # TODO: implement the logic for rule exceptions and one occurrence
        # modifications.
        self._check_for_one_occurrency_change()
        return super(RecurrentModel, self).unlink()

    @api.multi
    def write(self, values):
        # TODO: implement the logic for rule exceptions and one occurrence
        # modifications.
        self._check_for_one_occurrency_change()
        res = super(RecurrentModel, self).write(values)
        if 'no_update_rrule' not in self._context and not values.get('rrule'):
            self._update_rrule()
        return res

    @api.model
    def create(self, values):
        recurrence = super(RecurrentModel, self).create(values)
        if recurrence:
            recurrence._update_rrule()
        return recurrence

    @api.multi
    def _iter_occurrences_dates(self, start=None):
        '''Produce **all** the occurrences.

        Each item is a pair of `(date, record)`, where `date` is the date of
        an occurrence defined by `record.  Records are all the records in
        `self`.  `date` is always a `datetime`:class: object.

        Items are produced from `start` to end in order of occurrence.  Thus,
        items from different records can be intertwined.

        If `start` is None, use the first possible date (`date_from`) for all
        items.

        If any record in `self` is not recurrent it will yield a single
        instance with `date` equal to `date_from`.

        .. note:: This can be a potentially infinite iterator.

        '''
        from xoutil.future.itertools import merge

        def _iter_from(record):
            if record.is_recurrent:
                for date in record.iter_from(start=start):
                    #  The date comes first so that `merge` result be sorted by
                    #  date.
                    yield date, record
            else:
                yield normalize_dt(record.date_from), record

        return merge(*(_iter_from(record) for record in self))

    @api.model
    def _real_id2virtual(self, recurrent_date, real_id):
        if real_id and recurrent_date:
            recurrent_date = normalize_dt(recurrent_date)
            recurrent_date = recurrent_date.strftime(_V_DATE_FORMAT)
            return '%d-%s' % (real_id, recurrent_date)
        return real_id

    @api.model
    def _virtual_id2real(self, virtual_id=None):
        from xoutil.eight import string_types
        if virtual_id and isinstance(virtual_id, string_types):
            res = virtual_id.split('-', 1)
            if len(res) >= 2:
                real_id = res[0]
                return real_id
        return virtual_id

    @api.model
    def _extract_dates(self, virtual_id, duration):
        '''Extract start and end dates from the virtual id and duration.'''
        _, res = virtual_id.split('-', 1)
        start = datetime.strptime(res, _V_DATE_FORMAT)
        end = start + timedelta(hours=duration)
        start = normalize_dt_str(start)
        end = normalize_dt_str(end)
        return start, end

    @api.multi
    def _update_rrule(self):
        '''Update the rrule string representation.'''
        for record in self:
            record.rrule = str(record.get_rrule_from_description())

    @api.model
    def _check_for_one_occurrency_change(self):
        'Ensure only real ids.'
        real_ids = {self._virtual_id2real(x) for x in self.ids}
        if real_ids != set(self.ids):
            raise ValidationError('Expected only real ids')

    @api.multi
    def is_occurrency(self, day):
        '''Get True if day(date) is on one of virtuals ocurrences of real
        [ids] else False

        :param ids: real ids where search
        :param day: date to search
        :return: True or False

        '''
        day = datetime_user_to_server_tz(self._cr, self._uid, normalize_dt(day),
                                         self._context.get('tz'))
        day_str = normalize_dt_str(day.replace(hour=23, minute=59))
        res_ids = self.search([('id', 'in', self._ids), ('date', '<=', day_str)])
        for data in res_ids.read(['date', 'calendar_duration']):
            d_from = normalize_dt(data['date'])
            d_to = d_from + timedelta(hours=data.get('calendar_duration'))
            if d_from.date() <= day.date() <= d_to.date():
                return True
        return False
コード例 #7
0
class UnrealizedGLWizard(models.TransientModel):
    _name = "xopgi.unrealized_gl_wizard"

    def _get_valid_currencies(self):
        accounts = _get_valid_accounts()
        currencies = {account.currency_id.id for account in accounts}
        return [("id", "in", list(currencies))]

    def _get_currency(self):
        return Currency.search(self._get_valid_currencies(), limit=1)

    def _get_journal(self):
        return self.env.user.company_id.ugl_journal_id

    def _get_gain_account(self):
        return self.env.user.company_id.ugl_gain_account_id

    def _get_loss_account(self):
        return self.env.user.company_id.ugl_loss_account_id

    def _get_default_adjustments(self):
        currency = self._get_currency()
        accounts = _get_valid_accounts(currency)
        adjustments = []
        for account in accounts:
            with get_creator(self.env['xopgi.unrealized_gl_adjustment']) as c:
                c.update(account=account.id, wizard=self.id)
            adjustments.append(c.result)
        return adjustments

    close_date = fields.Date(string="Close Date",
                             required=True,
                             default=fields.Date.today)

    currency_id = fields.Many2one("res.currency",
                                  "Currency",
                                  required=True,
                                  default=_get_currency,
                                  domain=_get_valid_currencies)

    currency_rate = fields.Float(string="Currency Rate",
                                 readonly=True,
                                 digits=(15, 9),
                                 compute='_compute_all')

    journal_id = fields.Many2one("account.journal",
                                 string="Journal",
                                 required=True,
                                 domain=GENERAL_JOURNAL_DOMAIN,
                                 default=_get_journal,
                                 readonly=True)

    gain_account_id = fields.Many2one("account.account",
                                      string="Gain Account",
                                      required=True,
                                      domain=REGULAR_ACCOUNT_DOMAIN,
                                      default=_get_gain_account,
                                      readonly=True)

    loss_account_id = fields.Many2one("account.account",
                                      string="Loss Account",
                                      required=True,
                                      domain=REGULAR_ACCOUNT_DOMAIN,
                                      default=_get_loss_account,
                                      readonly=True)

    adjustments = fields.One2many(
        'xopgi.unrealized_gl_adjustment',
        'wizard',
        compute='_compute_all',
    )

    @api.depends('close_date', 'currency_id')
    def _compute_all(self):
        company_currency = self.env.user.company_id.currency_id
        for record in self:
            if any(record.currency_id):
                currency = record.currency_id.with_context(
                    date=self.close_date)
                record.currency_rate = currency.compute(
                    1,
                    company_currency,
                    round=False,
                )
                accounts = _get_valid_accounts(record.currency_id)
                adjustments = [
                    CREATE_RELATED(account=account, wizard=record)
                    for account in accounts
                ]
                record.adjustments = adjustments

    @api.multi
    def generate(self):
        from xoeuf.models.extensions import get_treeview_action
        moves = self._do_generate()
        return get_treeview_action(moves)

    @api.multi
    def _do_generate(self):
        moves = Move.browse()
        for adjustment in self.adjustments:
            gainloss = adjustment.gainloss
            if gainloss:
                sequence = self.journal_id.sequence_id
                account = adjustment.account
                name = 'UGL: %s' % self.close_date
                ref = 'UGL for AC: %s-%s at %s' % (account.code, account.name,
                                                   self.close_date)
                with get_creator(Move) as creator:
                    creator.update(
                        name=next_seq(sequence),
                        ref=ref,
                        journal_id=self.journal_id.id,
                        date=self.close_date,
                    )
                    if gainloss > 0:
                        creator.create(LINES_FIELD_NAME,
                                       name=name,
                                       account_id=self.gain_account_id.id,
                                       credit=gainloss)
                        creator.create(LINES_FIELD_NAME,
                                       name=name,
                                       account_id=account.id,
                                       debit=gainloss,
                                       amount_currency=0,
                                       currency_id=account.currency_id.id)
                    else:
                        creator.create(LINES_FIELD_NAME,
                                       name=name,
                                       account_id=self.loss_account_id.id,
                                       debit=-gainloss)
                        creator.create(LINES_FIELD_NAME,
                                       name=name,
                                       account_id=account.id,
                                       credit=-gainloss,
                                       amount_currency=0,
                                       currency_id=account.currency_id.id)

                # outside the with we get the result
                moves |= creator.result
        return moves