Ejemplo n.º 1
0
class MPTT(DeactivableMixin, tree(), ModelSQL, ModelView):
    'Modified Preorder Tree Traversal'
    __name__ = 'test.mptt'
    name = fields.Char('Name', required=True)
    parent = fields.Many2One('test.mptt', "Parent", select=True,
            left="left", right="right")
    left = fields.Integer('Left', required=True, select=True)
    right = fields.Integer('Right', required=True, select=True)
    childs = fields.One2Many('test.mptt', 'parent', 'Children')

    @staticmethod
    def order_sequence(tables):
        table, _ = tables[None]
        return [Case((table.sequence == Null, 0), else_=1), table.sequence]

    @staticmethod
    def default_left():
        return 0

    @staticmethod
    def default_right():
        return 0
Ejemplo n.º 2
0
class Group(tree(separator=' / '), ModelSQL, ModelView):
    'Domum Group'
    __name__ = 'domum.group'
    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)
    name = fields.Char('Name', required=True)
    description = fields.Char('Description')
    parent = fields.Many2One('domum.group', 'Parent', select=True,
        domain=[
            ('company', '=', Eval('company'))
        ], depends=['company'])
    childs = fields.One2Many('domum.group', 'parent', string='Childs',
        domain=[
            ('company', '=', Eval('company'))
        ], depends=['company'])
    units = fields.One2Many('domum.unit',
            'group', 'Units')
    order = fields.Integer('Order')

    @classmethod
    def __setup__(cls):
        super(Group, cls).__setup__()
        cls._order = [
            ('order', 'ASC'),
            ('name', 'ASC'),
            ]

    @staticmethod
    def default_company():
        return Transaction().context.get('company')
Ejemplo n.º 3
0
class Tree(tree(separator=' / '), ModelSQL):
    "Tree"
    __name__ = 'test.tree'
    name = fields.Char("Name")
    parent = fields.Many2One('test.tree', "Parent")
Ejemplo n.º 4
0
class Polytree(tree(parent='parents'), ModelSQL):
    "PolyTree"
    __name__ = 'test.polytree'
    name = fields.Char("Name")
    parents = fields.Many2Many('test.polytree.edge', 'parent', 'child',
                               "Parents")
Ejemplo n.º 5
0
class TmiGroup(ActivePeriodMixin, tree(), ModelView, ModelSQL):
    'TMI Group'
    __name__ = 'tmi.group'

    name = fields.Function(fields.Char('Name'),
        'get_name', searcher='search_meta_field')
    code = fields.Function(fields.Char('Code'),
        'get_code', searcher='search_meta_field')
    #active = fields.Boolean('Active')
    meta = fields.Many2One('tmi.meta.group', 'Meta', ondelete="RESTRICT",
        required=True, 
        domain=[
            ('company', '=', Eval('company')),
            ('type','in',['church','small_group'])
            ], depends=['','company'])
    type = fields.Function(fields.Selection(
        [
        ('church','Church'),
        ('small_group','Small Group'),
        ]
        ,'Type'), 'get_type', searcher='search_meta_field')
    parent_type = fields.Function(fields.Char('Parent Type'),
        'get_parent_type', searcher='search_meta_field')
    parent = fields.Function(fields.Many2One('tmi.meta.group', 'Parent',
        ondelete="RESTRICT",
        domain=[
            #'OR',[ ('company','=',Eval('company',-1)), 
            #    ('company', 'in',Eval('company.childs',[])),
            #    ],
            ('type','=',Eval('parent_type',-1))
            ],
        depends=['parent_type','company']),
        'get_parent', searcher='search_meta_field')
    childs = fields.Function(fields.One2Many('tmi.group', 'parent', 'Children',
        domain=[
            'OR',[ ('company','=',Eval('company',-1)), 
                ('company', 'in',Eval('company.childs',[])),
                ],
            ],
        depends=['company']),
        'get_childs', searcher='search_meta_field')
    company = fields.Function(fields.Many2One('company.company', 'Company', required=True,
            ondelete="RESTRICT"),
        'get_company', searcher='search_meta_field')
    currency = fields.Function(fields.Many2One('currency.currency',
        'Currency'), 'get_currency')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
        'get_currency_digits')

    baptism = fields.Function(fields.Numeric('Baptism',
        digits=(16,0)), 'get_balance')
    small_group = fields.Function(fields.Numeric('Small Group',
        digits=(16,0)), 'get_balance')
    tithe = fields.Function(fields.Numeric('Tithe',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    offering = fields.Function(fields.Numeric('Offering',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    praise_thanksgiving = fields.Function(fields.Numeric('Praise and Thanksgiving', 
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    gathering = fields.Function(fields.Numeric('Gathering',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    church_planting = fields.Function(fields.Numeric('Church Planting',
        digits=(16,0)), 'get_balance')
    organizing_church = fields.Function(fields.Numeric('Organizing Church',
        digits=(16,0)), 'get_balance')

    @classmethod
    def __setup__(cls):
        super(TmiGroup, cls).__setup__()
        t = cls.__table__()
        cls._order.insert(0, ('meta', 'ASC'))
        cls._sql_constraints = [
            ('meta_uniq', Unique(t, t.meta),
             'The meta group must be unique.')
        ]

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

    def get_name(self, name=None):
        if self.meta: 
            return self.meta.name

    def get_code(self, name=None):
        if self.meta: 
            return self.meta.code 

    def get_type(self, name=None):
        if self.meta:
            return self.meta.type

    '''
    def get_type(self, value=None): 
        if value=='small_group': 
            return 'Small Group'
        if value=='church':
            return 'Church'
        if value=='district':
            return 'District'
        if value=='zone':
            return 'Zone'
        if value=='field':
            return 'Field'
        if value=='union':
            return 'Union'
        if value=='division':
            return 'Division'
        if value=='conference':
            return 'Conference'
        return None

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

    def get_parent(self, name=None):
        if self.meta:
            if self.meta.parent: 
                return self.meta.parent.id
        return None

    def get_childs(self, name=None):
        if self.meta: 
            pool = Pool()
            Group = pool.get('tmi.group') 
            MetaGroup = pool.get('tmi.meta.group')

            meta_groups = MetaGroup.search([('id','=',self.meta.id)])
            meta_childs = []
            if len(meta_groups)==1: 
                meta_childs = meta_groups[0].childs
            if meta_childs is not []:
                childs = []
                for meta in meta_childs: 
                    groups = Group.search([('meta','=',meta.id)])
                    if len(groups)==1: 
                        childs.append(groups[0].id)
                return childs 
            return []
        else:
            return []

    def get_company(self, name=None):
        if self.meta: 
            return self.meta.company.id 

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

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

    @fields.depends('meta','code','type','parent','childs','company')
    def on_change_meta(self, name=None):
        self.name = None
        self.code = None
        self.type = None
        self.parent = None
        self.parent_type = None
        self.childs = []
        self.company = Transaction().context.get('company')
        if self.meta:
            pool = Pool()
            Group = pool.get('tmi.group') 
            MetaGroup = pool.get('tmi.meta.group')
            self.name = self.meta.name 
            self.code = self.meta.code
            self.type = self.meta.type
            self.parent_type = self.meta.parent_type
            self.company = self.meta.company 
            groups = Group.search([('meta','=',self.meta.parent.id)])
            if len(groups)==1:
                self.parent = groups[0].id
            meta_groups = MetaGroup.search([('id','=',self.meta.id)])
            meta_childs = []
            if len(meta_groups)==1: 
                meta_childs = meta_groups[0].childs
            if meta_childs is not []:
                childs = []
                for meta in meta_childs: 
                    groups = Group.search([('meta','=',meta.id)])
                    if len(groups)==1: 
                        childs.append(groups[0].id)
                self.childs = childs 

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

    def get_currency_digits(self, name):
        return self.company.currency.digits

    @fields.depends('company','currency','currency_digits')
    def on_change_company(self, name=None):
        if self.company: 
            self.currency = self.company.currency.id
            self.currency_digits = self.company.currency.digits

    def get_parent_type(self, name):
        if self.type=='small_group':
            return 'church'
        if self.type=='church':
            return 'district'
        if self.type=='district':
            return 'zone'
        if self.type=='zone':
            return 'field'
        if self.type=='field':
            return 'union'
        if self.type=='union':
            return 'division'
        if self.type=='division':
            return 'conference'
        return None

    @fields.depends('type','parent_type')
    def on_change_type(self):
        if self.type=='small_group':
            self.parent_type = 'church'
        if self.type=='church':
            self.parent_type = 'district'
        if self.type=='district':
            self.parent_type = 'zone'
        if self.type=='zone':
            self.parent_type = 'field'
        if self.type=='field':
            self.parent_type = 'union'
        if self.type=='union':
            self.parent_type = 'division'
        if self.type=='division':
            self.parent_type = 'conference'
        return None

    @staticmethod
    def default_active():
        return True 

    @staticmethod
    def default_type():
        return 'small_group'

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

    @classmethod
    def get_group_baptism(cls, groups, names):
        '''
        Function to compute baptism for TMI Group.        
        '''
        pool = Pool()
        MoveLine = pool.get('tmi.move.line')
        cursor = Transaction().connection.cursor()

        result = {}
        ids = [a.id for a in groups]
        for name in names:
            if name not in {'baptism'}:
                raise ValueError('Unknown name: %s' % name)
            result[name] = dict((i, Decimal(0)) for i in ids)

        table = cls.__table__()
        line = MoveLine.__table__()
        line_query, fiscalyear_ids = MoveLine.query_get(line)
        columns = [table.id]
        for name in names:
            columns.append(Sum(Coalesce(Column(line, name), 0)))
        for sub_ids in grouped_slice(ids):
            red_sql = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.join(line, 'LEFT',
                    condition=line.group == table.id
                    ).select(*columns,
                    where=red_sql & line_query,
                    group_by=table.id))
            for row in cursor.fetchall():
                group_id = row[0]
                for i, name in enumerate(names, 1):
                    # SQLite uses float for SUM
                    if not isinstance(row[i], Decimal):
                        result[name][group_id] = Decimal(str(row[i]))
                    else:
                        result[name][group_id] = row[i]
        for group in groups:
            for name in names:
                exp = Decimal(str(10.0 ** -group.currency_digits))
                result[name][group.id] = (
                    result[name][group.id].quantize(exp))

        return result

    @classmethod
    def get_balance(cls, groups, names):
        '''
        Function to compute tithe, baptism, church_planting, gathering, small_group, 
        organizing_church, praise_thanksgiving, offering for TMI Group.        
        '''
        pool = Pool()
        MoveLine = pool.get('tmi.move.line')
        cursor = Transaction().connection.cursor()

        result = {}
        ids = [a.id for a in groups]
        for name in names:
            if name not in {'baptism','tithe', 'church_planting', \
                    'gathering','small_group', 'organizing_church', \
                    'praise_thanksgiving', 'offering'}:
                raise ValueError('Unknown name: %s' % name)
            result[name] = dict((i, Decimal(0)) for i in ids)

        table = cls.__table__()
        line = MoveLine.__table__()
        #line_query = MoveLine.query_get(line)
        line_query, fiscalyear_ids = MoveLine.query_get(line)

        columns = [table.id]
        for name in names:
            columns.append(Sum(Coalesce(Column(line, name), 0)))
        for sub_ids in grouped_slice(ids):
            red_sql = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.join(line, 'LEFT',
                    condition=line.group == table.id
                    ).select(*columns,
                    where=red_sql & line_query,
                    group_by=table.id))
            for row in cursor.fetchall():
                group_id = row[0]
                for i, name in enumerate(names, 1):
                    # SQLite uses float for SUM
                    if not isinstance(row[i], Decimal):
                        result[name][group_id] = Decimal(str(row[i]))
                    else:
                        result[name][group_id] = row[i]
        for group in groups:
            for name in names:
                exp = Decimal(str(10.0 ** -group.currency_digits))
                result[name][group.id] = (
                    result[name][group.id].quantize(exp))
        return result
Ejemplo n.º 6
0
class Package(tree(), ModelSQL, ModelView):
    'Stock Package'
    __name__ = 'stock.package'
    _rec_name = 'code'
    code = fields.Char('Code', select=True, readonly=True, required=True)
    type = fields.Many2One('stock.package.type', 'Type', required=True)
    shipment = fields.Reference('Shipment',
                                selection='get_shipment',
                                select=True)
    moves = fields.One2Many('stock.move',
                            'package',
                            'Moves',
                            domain=[
                                ('shipment', '=', Eval('shipment')),
                                ('to_location.type', 'in',
                                 ['customer', 'supplier']),
                                ('state', '!=', 'cancel'),
                            ],
                            add_remove=[
                                ('package', '=', None),
                            ],
                            depends=['shipment'])
    parent = fields.Many2One('stock.package',
                             'Parent',
                             select=True,
                             ondelete='CASCADE',
                             domain=[('shipment', '=', Eval('shipment'))],
                             depends=['shipment'])
    children = fields.One2Many('stock.package',
                               'parent',
                               'Children',
                               domain=[('shipment', '=', Eval('shipment'))],
                               depends=['shipment'])

    @staticmethod
    def _get_shipment():
        'Return list of Model names for shipment Reference'
        return [
            'stock.shipment.out',
            'stock.shipment.in.return',
        ]

    @classmethod
    def get_shipment(cls):
        pool = Pool()
        Model = pool.get('ir.model')
        models = cls._get_shipment()
        models = Model.search([
            ('model', 'in', models),
        ])
        return [(None, '')] + [(m.model, m.name) for m in models]

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

        vlist = [v.copy() for v in vlist]
        config = Config(1)
        for values in vlist:
            values['code'] = Sequence.get_id(config.package_sequence)
        return super(Package, cls).create(vlist)
Ejemplo n.º 7
0
class Group(DeactivableMixin, tree(), ModelSQL, ModelView):
    "Group"
    __name__ = "res.group"
    name = fields.Char('Name', required=True, select=True, translate=True)
    users = fields.Many2Many('res.user-res.group', 'group', 'user', 'Users')
    parent = fields.Many2One(
        'res.group', "Parent",
        help="The group to inherit accesses from.")
    model_access = fields.One2Many('ir.model.access', 'group',
       'Access Model')
    field_access = fields.One2Many('ir.model.field.access', 'group',
        'Access Field')
    buttons = fields.Many2Many(
        'ir.model.button-res.group', 'group', 'button', "Buttons")
    rule_groups = fields.Many2Many('ir.rule.group-res.group',
       'group', 'rule_group', 'Rules',
       domain=[('global_p', '!=', True), ('default_p', '!=', True)])
    menu_access = MenuMany2Many('ir.ui.menu-res.group',
       'group', 'menu', 'Access Menu')

    @classmethod
    def __setup__(cls):
        super(Group, cls).__setup__()
        table = cls.__table__()
        cls._sql_constraints += [
            ('name_uniq', Unique(table, table.name),
                'The name of the group must be unique!')
        ]
        cls._order.insert(0, ('name', 'ASC'))

    @classmethod
    def write(cls, *args):
        super().write(*args)
        pool = Pool()
        # Restart the cache on the domain_get method
        pool.get('ir.rule')._domain_get_cache.clear()
        # Restart the cache for get_groups
        pool.get('res.user')._get_groups_cache.clear()
        # Restart the cache for model access and view
        pool.get('ir.model.access')._get_access_cache.clear()
        pool.get('ir.model.field.access')._get_access_cache.clear()
        ModelView._fields_view_get_cache.clear()

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

        new_groups = []
        for group in groups:
            i = 1
            while True:
                name = '%s (%d)' % (group.name, i)
                if not cls.search([('name', '=', name)], order=[]):
                    break
                i += 1
            default['name'] = name
            new_groups.extend(super(Group, cls).copy([group], default=default))
        return new_groups

    @classmethod
    def group_parent_all_cte(cls):
        group = cls.__table__()
        parents = With('id', 'parent', recursive=True)
        parents.query = group.select(group.id, group.parent)
        parents.query |= group.select(group.id, group.id)
        parents.query |= (group
            .join(parents, condition=group.parent == parents.id)
            .select(group.id, parents.parent))
        return parents
Ejemplo n.º 8
0
class Todo(Workflow, ModelSQL, ModelView, sequence_ordered(),
           tree(separator=' / ')):
    'TODO task'
    __name__ = 'todo.todo'

    _states = {
        'readonly': Equal(Eval('state'), 'done'),
    }
    _depends = ['state']

    name = fields.Char('Name', required=True, states=_states, depends=_depends)
    date = fields.Function(fields.DateTime('Date'), 'get_date')
    limit_date = fields.DateTime('Limit Date',
                                 states=_states,
                                 depends=_depends)
    limit_state = fields.Function(fields.Integer('Limit state'),
                                  'get_limit_state')
    finish_date = fields.DateTime('Finish Date',
                                  states={
                                      'readonly': Equal(Eval('state'), 'done'),
                                      'invisible':
                                      Equal(Eval('state'), 'open')
                                  },
                                  depends=_depends)
    user = fields.Function(fields.Many2One('res.user', 'User'), 'get_user')
    parent = fields.Many2One('todo.todo',
                             'Parent',
                             select=True,
                             domain=[('create_uid', '=', Eval('create_uid'))],
                             states=_states,
                             depends=_depends + ['create_uid'])
    childs = fields.One2Many('todo.todo',
                             'parent',
                             string='Childs',
                             states=_states,
                             depends=_depends)
    childs_open = fields.One2Many('todo.todo',
                                  'parent',
                                  string='Childs Open',
                                  states=_states,
                                  depends=_depends,
                                  filter=[('state', '=', 'open')])
    description = fields.Text('Description', states=_states, depends=_depends)
    state = fields.Selection([
        ('open', 'Open'),
        ('done', 'Done'),
    ],
                             'State',
                             readonly=True,
                             required=True)

    del _states, _depends

    @classmethod
    def __setup__(cls):
        super(Todo, cls).__setup__()
        cls._order = [
            ('sequence', 'ASC'),
            ('create_date', 'DESC'),
            ('id', 'DESC'),
        ]

        cls._transitions |= set((
            ('open', 'done'),
            ('done', 'open'),
        ))

        cls._buttons.update({
            'done': {
                'invisible': In(Eval('state'), ['done']),
            },
            'open': {
                'invisible': In(Eval('state'), ['open']),
            },
        })

    @classmethod
    def view_attributes(cls):
        return [
            ('/tree/field[@name="name"]', 'visual',
             If(And(Eval('limit_state', 0) > 0,
                    Eval('state', '') == 'open'),
                If(Eval('limit_state', 0) > 1, 'danger', 'warning'), '')),
        ]

    @staticmethod
    def default_state():
        return 'open'

    def get_limit_state(self, name):
        pool = Pool()
        Company = pool.get('company.company')
        timezone_str = None
        res = 0
        if self.limit_date:
            company_id = Transaction().context.get('company')
            if company_id:
                timezone_str = Company(company_id).timezone

            date = self.limit_date.astimezone(timezone(timezone_str))
            curr_date = datetime.datetime.now(timezone(timezone_str))

            date = date.date()
            curr_date = curr_date.date()
            if date == curr_date:
                res = 1  # Warning
            elif date < curr_date:
                res = 2  # Danger
        return res

    def get_date(self, name):
        return self.create_date.replace(microsecond=0)

    def get_user(self, name):
        return self.create_uid.id

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

    @classmethod
    @ModelView.button
    @Workflow.transition('open')
    def open(cls, todos):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('done')
    def done(cls, todos):
        finish_date = datetime.datetime.now()
        to_done = cls._set_done(todos, finish_date, False)
        cls.save(to_done)

    @classmethod
    def _set_done(cls, todos, finish_date, done_childs):
        pool = Pool()
        Warning = pool.get('res.user.warning')
        to_done = []
        for todo in todos:
            todo.state = 'done'
            todo.finish_date = finish_date
            to_done.append(todo)
            if todo.childs:
                if not done_childs:
                    msg_id = 'todo_done_childs_' + str(todo.id)
                    if Warning.check(msg_id):
                        raise UserWarning(
                            msg_id,
                            gettext('todo.msg_todo_done_childs',
                                    todo=todo.rec_name))
                to_done += cls._set_done(todo.childs, finish_date, True)
        return to_done
Ejemplo n.º 9
0
class Package(tree(), ModelSQL, ModelView):
    'Stock Package'
    __name__ = 'stock.package'
    _rec_name = 'code'
    code = fields.Char('Code', select=True, readonly=True, required=True)
    type = fields.Many2One('stock.package.type',
                           "Type",
                           required=True,
                           states={
                               'readonly': Eval('state') == 'closed',
                           },
                           depends=['state'])
    shipment = fields.Reference("Shipment",
                                selection='get_shipment',
                                select=True,
                                states={
                                    'readonly': Eval('state') == 'closed',
                                },
                                depends=['state'])
    moves = fields.One2Many('stock.move',
                            'package',
                            'Moves',
                            domain=[
                                ('shipment', '=', Eval('shipment')),
                                ('to_location.type', 'in',
                                 ['customer', 'supplier']),
                                ('state', '!=', 'cancel'),
                            ],
                            add_remove=[
                                ('package', '=', None),
                            ],
                            states={
                                'readonly': Eval('state') == 'closed',
                            },
                            depends=['shipment', 'state'])
    parent = fields.Many2One('stock.package',
                             "Parent",
                             select=True,
                             ondelete='CASCADE',
                             domain=[
                                 ('shipment', '=', Eval('shipment')),
                             ],
                             states={
                                 'readonly': Eval('state') == 'closed',
                             },
                             depends=['shipment', 'state'])
    children = fields.One2Many('stock.package',
                               'parent',
                               'Children',
                               domain=[
                                   ('shipment', '=', Eval('shipment')),
                               ],
                               states={
                                   'readonly': Eval('state') == 'closed',
                               },
                               depends=['shipment', 'state'])
    state = fields.Function(
        fields.Selection([
            ('open', "Open"),
            ('closed', "Closed"),
        ], "State"), 'on_change_with_state')

    @staticmethod
    def _get_shipment():
        'Return list of Model names for shipment Reference'
        return [
            'stock.shipment.out',
            'stock.shipment.in.return',
        ]

    @classmethod
    def get_shipment(cls):
        pool = Pool()
        Model = pool.get('ir.model')
        models = cls._get_shipment()
        models = Model.search([
            ('model', 'in', models),
        ])
        return [(None, '')] + [(m.model, m.name) for m in models]

    @fields.depends('shipment')
    def on_change_with_state(self, name=None):
        if (self.shipment
                and self.shipment.state in {'packed', 'done', 'cancel'}):
            return 'closed'
        return 'open'

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

        vlist = [v.copy() for v in vlist]
        config = Config(1)
        for values in vlist:
            values['code'] = Sequence.get_id(config.package_sequence)
        return super(Package, cls).create(vlist)
Ejemplo n.º 10
0
class WorkCenter(DeactivableMixin, tree(separator=' / '), ModelSQL, ModelView):
    'Work Center'
    __name__ = 'production.work.center'
    name = fields.Char('Name', required=True, translate=True)
    parent = fields.Many2One('production.work.center', 'Parent', select=True,
        domain=[
            ('company', '=', Eval('company', -1)),
            ('warehouse', '=', Eval('warehouse', -1)),
            ],
        depends=['company', 'warehouse'])
    children = fields.One2Many('production.work.center', 'parent', 'Children',
        domain=[
            ('company', '=', Eval('company', -1)),
            ('warehouse', '=', Eval('warehouse', -1)),
            ],
        depends=['company', 'warehouse'])
    category = fields.Many2One('production.work.center.category', 'Category')
    cost_price = fields.Numeric('Cost Price', digits=price_digits,
        states={
            'required': Bool(Eval('cost_method')),
            },
        depends=['cost_method'])
    cost_method = fields.Selection([
            ('', ''),
            ('cycle', 'Per Cycle'),
            ('hour', 'Per Hour'),
            ], 'Cost Method',
        states={
            'required': Bool(Eval('cost_price')),
            },
        depends=['cost_price'])
    company = fields.Many2One('company.company', 'Company', required=True,
        select=True)
    warehouse = fields.Many2One('stock.location', 'Warehouse', required=True,
        domain=[
            ('type', '=', 'warehouse'),
            ])

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

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

    @classmethod
    def default_warehouse(cls):
        Location = Pool().get('stock.location')
        return Location.get_default_warehouse()

    @classmethod
    def get_picker(cls):
        """Return a method that picks a work center
        for the category and the parent"""
        cache = {}

        def picker(parent, category):
            key = (parent, category)
            if key not in cache:
                work_centers = cls.search([
                        ('parent', 'child_of', [parent.id]),
                        ('category', '=', category.id),
                        ])
                if not work_centers:
                    raise PickerError(
                        gettext('production_work.msg_missing_work_center',
                            category=category.rec_name,
                            parent=parent.rec_name))
                cache[key] = work_centers
            return random.choice(cache[key])
        return picker
Ejemplo n.º 11
0
class TreeWildcard(tree(separator='\\'), ModelSQL):
    "Tree separator wildcard"
    __name__ = 'test.tree_wildcard'
    name = fields.Char("Name")
    parent = fields.Many2One('test.tree_wildcard', "Parent")
Ejemplo n.º 12
0
class BudgetMixin(BalanceMixin, tree(), ModelSQL, ModelView):
    name = fields.Char("Name", required=True,
        help="The main identifier of this budget.")
    company = fields.Many2One('company.company', "Company", required=True,
        domain=[
            ('id', If(Eval('context', {}).contains('company'), '=', '!='),
                Eval('context', {}).get('company', -1)),
            ], select=True,
        help="Make the budget belong to the company.")
    left = fields.Integer("Left", required=True, select=True)
    right = fields.Integer("Right", required=True, select=True)

    @classmethod
    def _childs_domain(cls):
        return [('company', '=', Eval('company'))]

    @classmethod
    def _childs_depends(cls):
        return ['company']

    @classmethod
    def __setup__(cls):
        if not hasattr(cls, 'parent'):
            domain = cls._childs_domain()
            depends = cls._childs_depends()
            cls.parent = fields.Many2One(cls.__name__, "Parent", select=True,
                help="Add the budget below the parent.",
                left='left', right='right', ondelete='RESTRICT',
                domain=domain, depends=depends)
            cls.children = fields.One2Many(cls.__name__, 'parent', "Children",
                help="Add children below the budget.",
                domain=domain, depends=depends)
        super(BudgetMixin, cls).__setup__()
        cls._buttons.update({
                'copy_budget': {
                    },
                })
        cls._error_messages.update({
                'invalid_children_amount': (
                    'The children amount "%(children_amount)s" of budget '
                    '"%(budget)s" can not be higher than its own amount '
                    '"%(amount)s".'
                    ),
                })

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

    @classmethod
    def default_left(cls):
        return 0

    @classmethod
    def default_right(cls):
        return 0

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

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

    '''
    def get_rec_name(self, name):
        if self.parent:
            return self.parent.get_rec_name(name) + '\\' + self.name
        else:
            return self.name

    '''

    '''
    @classmethod
    def search_rec_name(cls, name, clause):
        if isinstance(clause[2], basestring):
            values = clause[2].split('\\')
            values.reverse()
            domain = []
            field = 'name'
            for name in values:
                domain.append((field, clause[1], name.strip()))
                field = 'parent.' + field
        else:
            domain = [('name',) + tuple(clause[1:])]
        ids = [w.id for w in cls.search(domain, order=[])]
        return [('parent', 'child_of', ids)]
    '''
    @classmethod 
    def search_rec_name(cls, name, clause):
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [bool_op,
            ('name',) + tuple(clause[1:]),
            (cls._rec_name,) + tuple(clause[1:]),
            ]

    @classmethod
    def copy_budget(cls, budgets):
        raise NotImplementedError

    @classmethod
    def copy(cls, records, default=None):
        if default is None:
            default = {}
        default.setdefault('left', 0)
        default.setdefault('right', 0)
        return super(BudgetMixin, cls).copy(records, default=default)

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

    def check_amounts(self):
        children_amount = sum((c.amount for c in self.children), Decimal(0))
        if abs(children_amount) > abs(self.amount):
            self.raise_user_error('invalid_children_amount', {
                    'children_amount': children_amount,
                    'amount': self.amount,
                    'budget': self.rec_name,
                    })
Ejemplo n.º 13
0
 class ClassificationTreeMixin(tree(separator=' / '), ClassificationMixin):
     __slots__ = ()
     parent = fields.Many2One(name, 'Parent', select=True)
     childs = fields.One2Many(name, 'parent', 'Children')
Ejemplo n.º 14
0
class Account(DeactivableMixin, ModelSQL, ModelView, tree(separator='\\')):
    'Analytic Account'
    __name__ = 'analytic_account.account'

    template = fields.Many2One('analytic_account.account.template', 'Template')
    is_consolidated = fields.Boolean('Consolidated Indicator')
    is_current_capital = fields.Boolean('Current Capital')
    is_current_asset = fields.Boolean('Current Asset')
    is_recommended_capital = fields.Boolean('Recommended Capital')
    is_cash = fields.Boolean('Cash and Banks')
    is_current_liability = fields.Boolean('Current Liability')
    is_revenue = fields.Boolean('Revenue')
    is_expense = fields.Boolean('Expense')
    financial_indicator = fields.Function(
        fields.Numeric('Financial Indicator',
                       digits=(16, Eval('currency_digits', 2))),
        'get_financial_indicator')
    custom_balance = fields.Function(
        fields.Numeric('Custom Balance',
                       digits=(16, Eval('currency_digits', 1)),
                       depends=['currency_digits']), 'get_custom_balance')

    @classmethod
    def __setup__(cls):
        super(Account, cls).__setup__()
        cls.name.translate = False

    def get_current_capital(self):
        pool = Pool()
        Date = pool.get('ir.date')
        AccountType = pool.get('account.account.type')

        today = Date.today()
        company = Transaction().context.get('company')
        current_capital = current_liability = Decimal('0.0')

        current_capitals = AccountType.search([('company', '=', company),
                                               ('name', '=',
                                                '1) ACTIVOS CORRIENTES')])
        if len(current_capitals) == 1:
            current_capital = current_capitals[0].amount * Decimal('1.0')

        current_liabilities = AccountType.search([('company', '=', company),
                                                  ('name', '=',
                                                   '3) PASIVOS CORRIENTES')])
        if len(current_liabilities) == 1:
            current_liability = current_liabilities[0].amount * Decimal('1.0')

        balance = (current_capital - current_liability) * Decimal('1.0')
        return balance

    def get_cash(self):
        pool = Pool()
        Date = pool.get('ir.date')
        AccountType = pool.get('account.account.type')

        today = Date.today()
        company = Transaction().context.get('company')
        balance = Decimal('0.0')
        transaction = Transaction()
        context = Transaction().context
        total_cash = Decimal('0.0')

        if self.is_consolidated:
            companies = context.get('companies', [])
            for company in context.get('companies', []):
                with transaction.set_context(company=company['id']):
                    cash = Decimal('0.0')
                    accounts = AccountType.search([
                        ('company', '=', company['id']),
                        ('name', '=',
                         '10. Efectivo y Equivalencias de Efectivo')
                    ])
                    if len(accounts) == 1:
                        cash = accounts[0].amount * Decimal('1.0')
                total_cash += cash
            return total_cash
        else:
            accounts = AccountType.search([
                ('company', '=', company),
                ('name', '=', '10. Efectivo y Equivalencias de Efectivo')
            ])
            if len(accounts) == 1:
                balance = accounts[0].amount * Decimal('1.0')
        return balance

    def get_current_asset(self):
        pool = Pool()
        Date = pool.get('ir.date')
        AccountType = pool.get('account.account.type')

        today = Date.today()
        company = Transaction().context.get('company')

        transaction = Transaction()
        context = Transaction().context
        total_current_asset = current_asset = Decimal('0.0')

        today = Date.today()
        company = Transaction().context.get('company')
        to_date = Transaction().context.get('to_date')

        if self.is_consolidated:
            companies = context.get('companies', [])
            date = today if to_date is None else to_date
            for company in context.get('companies', []):
                with transaction.set_context(company=company['id'],
                                             posted=True,
                                             cumulate=True,
                                             date=date,
                                             to_date=date,
                                             from_date=None):
                    current_asset = Decimal('0.0')
                    current_assets = AccountType.search([
                        ('company', '=', company['id']),
                        ('name', '=', '1) ACTIVOS CORRIENTES')
                    ])
                    if len(current_assets) == 1:
                        current_asset = current_assets[0].amount * Decimal(
                            '1.0')
                total_current_asset += current_asset
            return total_current_asset
        else:
            date = today if to_date is None else to_date
            with transaction.set_context(
                    posted=True,
                    cumulate=True,
                    date=date,
                    to_date=date,
                    from_date=None,
            ):
                current_assets = AccountType.search([
                    ('company', '=', company),
                    ('name', '=', '1) ACTIVOS CORRIENTES')
                ])
                if len(current_assets) == 1:
                    current_asset = current_assets[0].amount * Decimal('1.0')
        return current_asset

    def get_current_liability(self):
        pool = Pool()
        Date = pool.get('ir.date')
        AccountType = pool.get('account.account.type')
        today = Date.today()
        liability = Decimal('0.0')
        transaction = Transaction()
        context = Transaction().context
        total_liability = liability = Decimal('0.0')

        company = Transaction().context.get('company')
        to_date = Transaction().context.get('to_date')

        if self.is_consolidated:
            companies = context.get('companies', [])
            date = today if to_date is None else to_date
            for company in context.get('companies', []):
                with transaction.set_context(
                        company=company['id'],
                        posted=True,
                        cumulate=True,
                        date=date,
                        to_date=date,
                        from_date=None,
                ):
                    liability = Decimal('0.0')
                    liabilities = AccountType.search([
                        ('company', '=', company['id']),
                        ('name', '=', '3) PASIVOS CORRIENTES')
                    ])
                    if len(liabilities) == 1:
                        liability = liabilities[0].amount * Decimal('1.0')
                total_liability += liability
            return total_liability
        else:
            current_liability = Decimal('0.0')
            date = today if to_date is None else to_date
            with transaction.set_context(
                    posted=True,
                    cumulate=True,
                    date=date,
                    to_date=date,
                    from_date=None,
            ):
                current_liabilities = AccountType.search([
                    ('company', '=', company),
                    ('name', '=', '3) PASIVOS CORRIENTES')
                ])
                if len(current_liabilities) == 1:
                    current_liability = current_liabilities[
                        0].amount * Decimal('1.0')

        return current_liability

    def get_revenues(self):
        pool = Pool()
        Date = pool.get('ir.date')
        AccountType = pool.get('account.account.type')
        today = Date.today()
        revenue = Decimal('0.0')
        transaction = Transaction()
        context = Transaction().context
        total_revenue = revenue = Decimal('0.0')

        if self.is_consolidated:
            companies = context.get('companies', [])
            for company in context.get('companies', []):
                with transaction.set_context(company=company['id']):
                    revenue = Decimal('0.0')
                    revenues = AccountType.search([
                        ('company', '=', company['id']),
                        ('name', '=', 'INGRESOS FINANCIEROS')
                    ])
                    if len(revenues) == 1:
                        revenue = revenues[0].amount * Decimal('1.0')
                total_revenue += revenue
            return total_revenue
        else:
            revenue = Decimal('0.0')
            company = Transaction().context.get('company')
            revenues = AccountType.search([('company', '=', company),
                                           ('name', '=',
                                            'INGRESOS FINANCIEROS')])
            if len(revenues) == 1:
                revenue = revenues[0].amount * Decimal('1.0')
        return revenue

    def get_expenses(self):
        pool = Pool()
        Date = pool.get('ir.date')
        AccountType = pool.get('account.account.type')
        today = Date.today()
        transaction = Transaction()
        context = Transaction().context
        total_expense = expense = Decimal('0.0')

        if self.is_consolidated:
            companies = context.get('companies', [])
            for company in context.get('companies', []):
                with transaction.set_context(company=company['id']):
                    expense = Decimal('0.0')
                    expenses = AccountType.search([
                        ('company', '=', company['id']),
                        ('name', '=', 'GASTOS FINANCIEROS')
                    ])
                    if len(expenses) == 1:
                        expense = expenses[0].amount * Decimal('1.0')
                total_expense += expense
            return total_expense
        else:
            company = Transaction().context.get('company')
            expense = Decimal('0.0')
            expenses = AccountType.search([('company', '=', company),
                                           ('name', '=', 'GASTOS FINANCIEROS')
                                           ])

            if len(expenses) == 1:
                expense = expenses[0].amount * Decimal('1.0')

        return expense

    def get_recommended_capital(self):
        pool = Pool()
        Date = pool.get('ir.date')
        Fiscalyear = pool.get('account.fiscalyear')
        Budget = pool.get('account.budget')

        today = Date.today()

        transaction = Transaction()
        context = Transaction().context
        company = context.get('company')
        balance = Decimal('0.0')

        if self.is_consolidated:
            companies = context.get('companies', [])
            for company in context.get('companies', []):
                total_amount = Decimal('0.0')
                with transaction.set_context(company=company['id']):
                    fiscalyears = Fiscalyear.search([
                        ('company', '=', company['id']),
                        ('start_date', '<=', today), ('end_date', '>=', today)
                    ])
                    fiscalyear = None
                    if len(fiscalyears) == 1:
                        fiscalyear = fiscalyears[0].id

                    budgets = Budget.search([('fiscalyear', '=', fiscalyear),
                                             ('company', '=', company['id']),
                                             ('parent', '=', None)])
                    if len(budgets) == 1:
                        budget = Budget(budgets[0].id)
                        balance += budget.children[1].amount * Decimal('0.15')
            #balance *= -1
        else:
            fiscalyear = Transaction().context.get('fiscalyear')
            if fiscalyear is not None:
                fiscalyears = Fiscalyear.search([('company', '=', company),
                                                 ('id', '=', fiscalyear)])
            else:
                fiscalyears = Fiscalyear.search([('company', '=', company),
                                                 ('start_date', '<=', today),
                                                 ('end_date', '>=', today)])
                if len(fiscalyears) == 1:
                    fiscalyear = fiscalyears[0].id

            budgets = Budget.search([('fiscalyear', '=', fiscalyear),
                                     ('company', '=', company),
                                     ('parent', '=', None)])

            if len(budgets) == 1:
                budget = Budget(budgets[0].id)
                print("BUDGET: ", str(budget))
                balance = budget.children[0].amount * Decimal('0.15')
                print("BALANCE: ", str(balance))
                #balance *= -1

        return balance

    def get_difference_between_childs(self):
        balance = first_child = second_child = 0
        if self.childs[0] is not None and self.childs[1] is not None:
            first_child = self.childs[0].custom_balance
            second_child = self.childs[1].custom_balance
            balance = first_child - second_child
        return balance

    @classmethod
    def get_custom_balance(cls, accounts, name):

        balances = {}
        for account in accounts:
            balance = Decimal()
            if account.is_current_capital == True:
                balance = account.get_difference_between_childs()
            elif account.is_recommended_capital == True:
                balance = account.get_recommended_capital()
            elif account.is_cash == True:
                balance = account.get_cash()
            elif account.is_current_liability == True:
                balance = account.get_current_liability()
            elif account.is_current_asset == True:
                balance = account.get_current_asset()
            elif account.is_revenue == True:
                balance = account.get_revenues()
            elif account.is_expense == True:
                balance = account.get_expenses()
            elif account.type == 'root':
                balance = account.get_difference_between_childs()
            if account.display_balance == 'credit-debit' and balance:
                balance *= -1
            exp = Decimal(str(10.0**-account.currency_digits))
            balances[account.id] = balance.quantize(exp)
        return balances

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

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

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

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

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

    def get_financial_indicator(self, name):
        if self.type == 'root':
            first_child = second_child = quotient = 0
            if self.childs is not None:
                first_child = Decimal(str(self.childs[0].custom_balance))
                second_child = Decimal(str(self.childs[1].custom_balance))
            if second_child != 0:
                quotient = first_child / second_child * 100
                return quotient
        credit = self.credit if self.credit else 0
        debit = self.debit if self.debit else 0
        if debit is not 0:
            return credit / debit
        return 0

    @staticmethod
    def default_is_consolidated():
        return False

    def update_analytic_account(self, template2account=None):
        '''
        Update recursively types based on template.
        template2type is a dictionary with template id as key and type id as
        value, used to convert template id into type. The dictionary is filled
        with new types
        '''
        if template2account is None:
            template2account = {}

        values = []
        childs = [self]
        while childs:
            for child in childs:
                if child.template:
                    vals = child.template._get_account_value()
                    if vals:
                        values.append([child])
                        values.append(vals)
                    template2account[child.template.id] = child.id
            childs = sum((c.childs for c in childs), ())
        if values:
            self.write(*values)
Ejemplo n.º 15
0
class Location(DeactivableMixin, tree(), ModelSQL, ModelView):
    "Stock Location"
    __name__ = 'stock.location'
    _default_warehouse_cache = Cache('stock.location.default_warehouse',
                                     context=False)

    name = fields.Char("Name", size=None, required=True, translate=True)
    code = fields.Char("Code",
                       size=None,
                       select=True,
                       help="The internal identifier used for the location.")
    address = fields.Many2One('party.address',
                              "Address",
                              states={
                                  'invisible': Eval('type') != 'warehouse',
                              })
    type = fields.Selection([
        ('supplier', 'Supplier'),
        ('customer', 'Customer'),
        ('lost_found', 'Lost and Found'),
        ('warehouse', 'Warehouse'),
        ('storage', 'Storage'),
        ('production', 'Production'),
        ('drop', 'Drop'),
        ('view', 'View'),
    ], "Location type")
    type_string = type.translated('type')
    parent = fields.Many2One("stock.location",
                             "Parent",
                             select=True,
                             left="left",
                             right="right",
                             help="Used to add structure above the location.")
    left = fields.Integer('Left', required=True, select=True)
    right = fields.Integer('Right', required=True, select=True)
    childs = fields.One2Many("stock.location",
                             "parent",
                             "Children",
                             help="Used to add structure below the location.")
    flat_childs = fields.Boolean(
        "Flat Children",
        help="Check to enforce a single level of children with no "
        "grandchildren.")
    warehouse = fields.Function(fields.Many2One('stock.location', 'Warehouse'),
                                'get_warehouse')
    input_location = fields.Many2One("stock.location",
                                     "Input",
                                     states={
                                         'invisible':
                                         Eval('type') != 'warehouse',
                                         'required':
                                         Eval('type') == 'warehouse',
                                     },
                                     domain=[
                                         ('type', '=', 'storage'),
                                         [
                                             'OR',
                                             ('parent', 'child_of',
                                              [Eval('id', -1)]),
                                             ('parent', '=', None),
                                         ],
                                     ],
                                     help="Where incoming stock is received.")
    output_location = fields.Many2One(
        "stock.location",
        "Output",
        states={
            'invisible': Eval('type') != 'warehouse',
            'required': Eval('type') == 'warehouse',
        },
        domain=[('type', '=', 'storage'),
                [
                    'OR', ('parent', 'child_of', [Eval('id', -1)]),
                    ('parent', '=', None)
                ]],
        help="Where outgoing stock is sent from.")
    storage_location = fields.Many2One(
        "stock.location",
        "Storage",
        states={
            'invisible': Eval('type') != 'warehouse',
            'required': Eval('type') == 'warehouse',
        },
        domain=[('type', 'in', ['storage', 'view']),
                [
                    'OR', ('parent', 'child_of', [Eval('id', -1)]),
                    ('parent', '=', None)
                ]],
        help="The top level location where stock is stored.")
    picking_location = fields.Many2One(
        'stock.location',
        'Picking',
        states={
            'invisible': Eval('type') != 'warehouse',
        },
        domain=[
            ('type', '=', 'storage'),
            ('parent', 'child_of', [Eval('storage_location', -1)]),
        ],
        help="Where stock is picked from.\n"
        "Leave empty to use the storage location.")
    lost_found_location = fields.Many2One(
        'stock.location',
        "Lost and Found",
        states={
            'invisible': Eval('type') != 'warehouse',
            'readonly': ~Eval('active'),
        },
        domain=[
            ('type', '=', 'lost_found'),
        ],
        help="Used, by inventories, when correcting stock levels "
        "in the warehouse.")
    waste_locations = fields.Many2Many(
        'stock.location.waste',
        'warehouse',
        'location',
        "Waste Locations",
        states={
            'invisible': Eval('type') != 'warehouse',
        },
        domain=[
            ('type', '=', 'lost_found'),
        ],
        help="The locations used for waste products from the warehouse.")
    waste_warehouses = fields.Many2Many(
        'stock.location.waste',
        'location',
        'warehouse',
        "Waste Warehouses",
        states={
            'invisible': Eval('type') != 'lost_found',
        },
        domain=[
            ('type', '=', 'warehouse'),
        ],
        help="The warehouses that use the location for waste products.")

    quantity = fields.Function(fields.Float(
        "Quantity",
        digits=(16, Eval('quantity_uom_digits', 2)),
        help="The amount of stock in the location."),
                               'get_quantity',
                               searcher='search_quantity')
    forecast_quantity = fields.Function(fields.Float(
        "Forecast Quantity",
        digits=(16, Eval('quantity_uom_digits', 2)),
        help="The amount of stock expected to be in the location."),
                                        'get_quantity',
                                        searcher='search_quantity')
    quantity_uom = fields.Function(
        fields.Many2One('product.uom', "Quantity UOM"), 'get_quantity_uom')
    quantity_uom_digits = fields.Function(
        fields.Integer("Quantity UOM Digits"), 'get_quantity_uom')
    cost_value = fields.Function(
        fields.Numeric("Cost Value",
                       digits=price_digits,
                       help="The value of the stock in the location."),
        'get_cost_value')

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

        parent_domain = [[
            'OR',
            ('parent.flat_childs', '=', False),
            ('parent', '=', None),
        ]]
        childs_domain = [
            If(Eval('flat_childs', False), ('childs', '=', None), ()),
        ]
        childs_mapping = cls._childs_domain()
        for type_, allowed_parents in cls._parent_domain().items():
            parent_domain.append(
                If(Eval('type') == type_, ('type', 'in', allowed_parents), ()))
            childs_domain.append(
                If(
                    Eval('type') == type_,
                    ('type', 'in', childs_mapping[type_]), ()))
        cls.parent.domain = parent_domain
        cls.childs.domain = childs_domain

    @classmethod
    def _parent_domain(cls):
        '''Returns a dict with location types as keys and a list of allowed
        parent location types as values'''
        return {
            'customer': ['customer'],
            'supplier': ['supplier'],
            'production': ['production'],
            'lost_found': ['lost_found'],
            'view': ['warehouse', 'view', 'storage'],
            'storage': ['warehouse', 'view', 'storage'],
            'warehouse': ['view'],
        }

    @classmethod
    def _childs_domain(cls):
        childs_domain = {}
        for type_, allowed_parents in cls._parent_domain().items():
            for parent in allowed_parents:
                childs_domain.setdefault(parent, [])
                childs_domain[parent].append(type_)
        return childs_domain

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

        table = cls.__table_handler__(module_name)
        table.index_action(['left', 'right'], 'add')

    @classmethod
    def validate_fields(cls, locations, field_names):
        super().validate_fields(locations, field_names)
        inactives = []
        for location in locations:
            location.check_type_for_moves(field_names)
            if 'active' in field_names and not location.active:
                inactives.append(location)
        cls.check_inactive(inactives)

    def check_type_for_moves(self, field_names=None):
        """ Check locations with moves have types compatible with moves. """
        pool = Pool()
        Move = pool.get('stock.move')
        if field_names and 'type' not in field_names:
            return
        invalid_move_types = ['warehouse', 'view']
        if self.type in invalid_move_types:
            # Use root to compute for all companies
            with Transaction().set_user(0):
                moves = Move.search([
                    [
                        'OR',
                        ('to_location', '=', self.id),
                        ('from_location', '=', self.id),
                    ],
                    ('state', 'not in', ['staging', 'draft']),
                ],
                                    order=[],
                                    limit=1)
            if moves:
                raise LocationValidationError(
                    gettext('stock.msg_location_invalid_type_for_moves',
                            location=self.rec_name,
                            type=self.type_string))

    @classmethod
    def check_inactive(cls, locations):
        "Check inactive location are empty"
        assert all(not l.active for l in locations)
        empty = cls.get_empty_locations(locations)
        non_empty = set(locations) - set(empty)
        if non_empty:
            raise LocationValidationError(
                gettext('stock.msg_location_inactive_not_empty',
                        location=next(iter(non_empty)).rec_name))

    @classmethod
    def get_empty_locations(cls, locations=None):
        pool = Pool()
        Move = pool.get('stock.move')
        if locations is None:
            locations = cls.search([])
        if not locations:
            return []
        location_ids = list(map(int, locations))
        # Use root to compute for all companies
        # and ensures inactive locations are in the query
        with Transaction().set_user(0), \
                Transaction().set_context(active_test=False):
            query = Move.compute_quantities_query(location_ids,
                                                  with_childs=True)
            quantities = Move.compute_quantities(query,
                                                 location_ids,
                                                 with_childs=True)
            empty = set(location_ids)
            for (location_id, product), quantity in quantities.items():
                if quantity:
                    empty.discard(location_id)
            for sub_ids in grouped_slice(list(empty)):
                sub_ids = list(sub_ids)
                moves = Move.search([
                    ('state', 'not in', ['done', 'cancelled']),
                    [
                        'OR',
                        ('from_location', 'in', sub_ids),
                        ('to_location', 'in', sub_ids),
                    ],
                ])
                for move in moves:
                    for location in [move.from_location, move.to_location]:
                        empty.discard(location.id)
        return cls.browse(empty)

    @staticmethod
    def default_left():
        return 0

    @staticmethod
    def default_right():
        return 0

    @classmethod
    def default_flat_childs(cls):
        return False

    @staticmethod
    def default_type():
        return 'storage'

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

    def get_warehouse(self, name):
        # Order by descending left to get the first one in the tree
        with Transaction().set_context(active_test=False):
            locations = self.search([
                ('parent', 'parent_of', [self.id]),
                ('type', '=', 'warehouse'),
            ],
                                    order=[('left', 'DESC')])
        if locations:
            return locations[0].id

    @classmethod
    def get_default_warehouse(cls):
        warehouse = Transaction().context.get('warehouse')
        if warehouse:
            return warehouse

        warehouse = cls._default_warehouse_cache.get(None, -1)
        if warehouse == -1:
            warehouses = cls.search([
                ('type', '=', 'warehouse'),
            ], limit=2)
            if len(warehouses) == 1:
                warehouse = warehouses[0].id
            else:
                warehouse = None
            cls._default_warehouse_cache.set(None, warehouse)
        return warehouse

    @property
    def lost_found_used(self):
        if self.warehouse:
            return self.warehouse.lost_found_location

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

    @classmethod
    def _get_quantity_grouping(cls):
        context = Transaction().context
        grouping, grouping_filter, key = (), (), []
        if context.get('product') is not None:
            grouping = ('product', )
            grouping_filter = ([context['product']], )
            key = (context['product'], )
        elif context.get('product_template') is not None:
            grouping = ('product.template', )
            grouping_filter = ([context['product_template']], )
            key = (context['product_template'], )
        return grouping, grouping_filter, key

    @classmethod
    def get_quantity(cls, locations, name):
        pool = Pool()
        Product = pool.get('product.product')
        Date_ = pool.get('ir.date')
        trans_context = Transaction().context

        def valid_context(name):
            return (trans_context.get(name) is not None
                    and isinstance(trans_context[name], int))

        context = {}
        if (name == 'quantity' and (trans_context.get(
                'stock_date_end', datetime.date.max) > Date_.today())):
            context['stock_date_end'] = Date_.today()

        if name == 'forecast_quantity':
            context['forecast'] = True
            if not trans_context.get('stock_date_end'):
                context['stock_date_end'] = datetime.date.max

        grouping, grouping_filter, key = cls._get_quantity_grouping()
        if not grouping:
            return {loc.id: None for loc in locations}

        pbl = {}
        for sub_locations in grouped_slice(locations):
            location_ids = [l.id for l in sub_locations]
            with Transaction().set_context(context):
                pbl.update(
                    Product.products_by_location(
                        location_ids,
                        grouping=grouping,
                        grouping_filter=grouping_filter,
                        with_childs=trans_context.get('with_childs', True)))

        return dict(
            (loc.id, pbl.get((loc.id, ) + key, 0)) for loc in locations)

    @classmethod
    def search_quantity(cls, name, domain):
        _, operator_, operand = domain
        operator_ = {
            '=': operator.eq,
            '>=': operator.ge,
            '>': operator.gt,
            '<=': operator.le,
            '<': operator.lt,
            '!=': operator.ne,
            'in': lambda v, l: v in l,
            'not in': lambda v, l: v not in l,
        }.get(operator_, lambda v, l: False)

        ids = []
        for location in cls.search([]):
            if operator_(getattr(location, name), operand):
                ids.append(location.id)
        return [('id', 'in', ids)]

    @classmethod
    def get_quantity_uom(cls, locations, name):
        pool = Pool()
        Product = pool.get('product.product')
        Template = pool.get('product.template')
        context = Transaction().context
        value = None
        uom = None
        if context.get('product') is not None:
            product = Product(context['product'])
            uom = product.default_uom
        elif context.get('product_template') is not None:
            template = Template(context['product_template'])
            uom = template.default_uom
        if uom:
            if name == 'quantity_uom':
                value = uom.id
            elif name == 'quantity_uom_digits':
                value = uom.digits
        return {l.id: value for l in locations}

    @classmethod
    def get_cost_value(cls, locations, name):
        pool = Pool()
        Product = pool.get('product.product')
        Template = pool.get('product.template')
        trans_context = Transaction().context
        cost_values = {l.id: None for l in locations}

        def valid_context(name):
            return (trans_context.get(name) is not None
                    and isinstance(trans_context[name], int))

        if not any(map(valid_context, ['product', 'product_template'])):
            return cost_values

        def get_record():
            if trans_context.get('product') is not None:
                return Product(trans_context['product'])
            else:
                return Template(trans_context['product_template'])

        context = {}
        if 'stock_date_end' in trans_context:
            # Use the last cost_price of the day
            context['_datetime'] = datetime.datetime.combine(
                trans_context['stock_date_end'], datetime.time.max)
            # The date could be before the product creation
            record = get_record()
            if record.create_date > context['_datetime']:
                return cost_values
        with Transaction().set_context(context):
            cost_price = get_record().cost_price
        # The template may have more than one product
        if cost_price is not None:
            for location in locations:
                cost_values[location.id] = round_price(
                    Decimal(str(location.quantity)) * cost_price)
        return cost_values

    @classmethod
    def _set_warehouse_parent(cls, locations):
        '''
        Set the parent of child location of warehouse if not set
        '''
        to_update = set()
        to_save = []
        for location in locations:
            if location.type == 'warehouse':
                if not location.input_location.parent:
                    to_update.add(location.input_location)
                if not location.output_location.parent:
                    to_update.add(location.output_location)
                if not location.storage_location.parent:
                    to_update.add(location.storage_location)
                if to_update:
                    for child_location in to_update:
                        child_location.parent = location
                        to_save.append(child_location)
                    to_update.clear()
        cls.save(to_save)

    @classmethod
    def create(cls, vlist):
        locations = super(Location, cls).create(vlist)
        cls._set_warehouse_parent(locations)
        cls._default_warehouse_cache.clear()
        return locations

    @classmethod
    def write(cls, *args):
        super(Location, cls).write(*args)
        locations = sum(args[::2], [])
        cls._set_warehouse_parent(locations)
        cls._default_warehouse_cache.clear()

        ids = [l.id for l in locations]
        warehouses = cls.search([('type', '=', 'warehouse'),
                                 [
                                     'OR',
                                     ('storage_location', 'in', ids),
                                     ('input_location', 'in', ids),
                                     ('output_location', 'in', ids),
                                 ]])

        fields = ('storage_location', 'input_location', 'output_location')
        wh2childs = {}
        for warehouse in warehouses:
            in_out_sto = (getattr(warehouse, f).id for f in fields)
            for location in locations:
                if location.id not in in_out_sto:
                    continue
                childs = wh2childs.setdefault(
                    warehouse.id,
                    cls.search([
                        ('parent', 'child_of', warehouse.id),
                    ]))
                if location not in childs:
                    raise LocationValidationError(
                        gettext('stock.msg_location_child_of_warehouse',
                                location=location.rec_name,
                                warehouse=warehouse.rec_name))

    @classmethod
    def delete(cls, *args):
        super().delete(*args)
        cls._default_warehouse_cache.clear()

    @classmethod
    def copy(cls, locations, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()

        res = []
        for location in locations:
            if location.type == 'warehouse':

                wh_default = default.copy()
                wh_default['type'] = 'view'
                wh_default['input_location'] = None
                wh_default['output_location'] = None
                wh_default['storage_location'] = None
                wh_default['childs'] = None

                new_location, = super(Location, cls).copy([location],
                                                          default=wh_default)

                with Transaction().set_context(
                        cp_warehouse_locations={
                            'input_location': location.input_location.id,
                            'output_location': location.output_location.id,
                            'storage_location': location.storage_location.id,
                        },
                        cp_warehouse_id=new_location.id):
                    cls.copy(location.childs,
                             default={'parent': new_location.id})
                cls.write([new_location], {
                    'type': 'warehouse',
                })
            else:
                new_location, = super(Location, cls).copy([location],
                                                          default=default)
                warehouse_locations = Transaction().context.get(
                    'cp_warehouse_locations') or {}
                if location.id in warehouse_locations.values():
                    cp_warehouse = cls(
                        Transaction().context['cp_warehouse_id'])
                    for field, loc_id in warehouse_locations.items():
                        if loc_id == location.id:
                            cls.write([cp_warehouse], {
                                field: new_location.id,
                            })

            res.append(new_location)
        return res

    @classmethod
    def view_attributes(cls):
        storage_types = Eval('type').in_(['storage', 'warehouse', 'view'])
        return super().view_attributes() + [
            ('/tree/field[@name="quantity"]', 'visual',
             If(storage_types &
                (Eval('quantity', 0) < 0), 'danger', ''), ['type']),
            ('/tree/field[@name="forecast_quantity"]', 'visual',
             If(storage_types &
                (Eval('forecast_quantity', 0) < 0), 'warning', ''), ['type']),
        ]
Ejemplo n.º 16
0
class Work(tree(parent='successors'), metaclass=PoolMeta):
    __name__ = 'project.work'
    predecessors = fields.Many2Many('project.predecessor_successor',
                                    'successor',
                                    'predecessor',
                                    'Predecessors',
                                    domain=[
                                        ('parent', '=', Eval('parent')),
                                        ('id', '!=', Eval('id')),
                                    ],
                                    depends=['parent', 'id'])
    successors = fields.Many2Many('project.predecessor_successor',
                                  'predecessor',
                                  'successor',
                                  'Successors',
                                  domain=[
                                      ('parent', '=', Eval('parent')),
                                      ('id', '!=', Eval('id')),
                                  ],
                                  depends=['parent', 'id'])
    leveling_delay = fields.Float("Leveling Delay", required=True)
    back_leveling_delay = fields.Float("Back Leveling Delay", required=True)
    allocations = fields.One2Many('project.allocation',
                                  'work',
                                  'Allocations',
                                  states={
                                      'invisible': Eval('type') != 'task',
                                  },
                                  depends=['type'])
    duration = fields.Function(
        fields.TimeDelta('Duration', 'company_work_time'),
        'get_function_fields')
    early_start_time = fields.DateTime("Early Start Time", readonly=True)
    late_start_time = fields.DateTime("Late Start Time", readonly=True)
    early_finish_time = fields.DateTime("Early Finish Time", readonly=True)
    late_finish_time = fields.DateTime("Late Finish Time", readonly=True)
    actual_start_time = fields.DateTime("Actual Start Time")
    actual_finish_time = fields.DateTime("Actual Finish Time")
    constraint_start_time = fields.DateTime("Constraint Start Time")
    constraint_finish_time = fields.DateTime("Constraint Finish Time")
    early_start_date = fields.Function(fields.Date('Early Start'),
                                       'get_function_fields')
    late_start_date = fields.Function(fields.Date('Late Start'),
                                      'get_function_fields')
    early_finish_date = fields.Function(fields.Date('Early Finish'),
                                        'get_function_fields')
    late_finish_date = fields.Function(fields.Date('Late Finish'),
                                       'get_function_fields')
    actual_start_date = fields.Function(fields.Date('Actual Start'),
                                        'get_function_fields',
                                        setter='set_function_fields')
    actual_finish_date = fields.Function(fields.Date('Actual Finish'),
                                         'get_function_fields',
                                         setter='set_function_fields')
    constraint_start_date = fields.Function(fields.Date('Constraint Start',
                                                        depends=['type']),
                                            'get_function_fields',
                                            setter='set_function_fields')
    constraint_finish_date = fields.Function(fields.Date('Constraint Finish',
                                                         depends=['type']),
                                             'get_function_fields',
                                             setter='set_function_fields')

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

    @staticmethod
    def default_leveling_delay():
        return 0.0

    @staticmethod
    def default_back_leveling_delay():
        return 0.0

    @classmethod
    def get_function_fields(cls, works, names):
        '''
        Function to compute function fields
        '''
        res = {}

        ids = [w.id for w in works]
        if 'duration' in names:
            all_works = cls.search([
                ('parent', 'child_of', ids),
            ])
            all_works = set(all_works)

            durations = {}
            id2work = {}
            leafs = set()
            for work in all_works:
                id2work[work.id] = work
                if not work.children:
                    leafs.add(work.id)

                total_allocation = 0
                effort = work.effort_duration or datetime.timedelta()
                if not work.allocations:
                    durations[work.id] = effort
                    continue
                for allocation in work.allocations:
                    total_allocation += allocation.percentage
                durations[work.id] = datetime.timedelta(
                    seconds=effort.total_seconds() /
                    (total_allocation / 100.0))

            while leafs:
                for work_id in leafs:
                    work = id2work[work_id]
                    all_works.remove(work)
                    if work.parent and work.parent.id in durations:
                        durations[work.parent.id] += durations[work_id]
                next_leafs = set(w.id for w in all_works)
                for work in all_works:
                    if not work.parent:
                        continue
                    if work.parent.id in next_leafs and work.parent in works:
                        next_leafs.remove(work.parent.id)
                leafs = next_leafs
            res['duration'] = durations

        fun_fields = ('early_start_date', 'early_finish_date',
                      'late_start_date', 'late_finish_date',
                      'actual_start_date', 'actual_finish_date',
                      'constraint_start_date', 'constraint_finish_date')
        db_fields = ('early_start_time', 'early_finish_time',
                     'late_start_time', 'late_finish_time',
                     'actual_start_time', 'actual_finish_time',
                     'constraint_start_time', 'constraint_finish_time')

        for fun_field, db_field in zip(fun_fields, db_fields):
            if fun_field in names:
                values = {}
                for work in works:
                    values[work.id] = getattr(work, db_field) \
                        and getattr(work, db_field).date() or None
                res[fun_field] = values

        return res

    @classmethod
    def set_function_fields(cls, works, name, value):
        fun_fields = ('actual_start_date', 'actual_finish_date',
                      'constraint_start_date', 'constraint_finish_date')
        db_fields = ('actual_start_time', 'actual_finish_time',
                     'constraint_start_time', 'constraint_finish_time')
        for fun_field, db_field in zip(fun_fields, db_fields):
            if fun_field == name:
                cls.write(
                    works, {
                        db_field:
                        (value
                         and datetime.datetime.combine(value, datetime.time())
                         or None),
                    })
                break

    @property
    def hours(self):
        if not self.duration:
            return 0
        return self.duration.total_seconds() / 60 / 60

    @classmethod
    def add_minutes(cls, company, date, minutes):
        minutes = int(round(minutes))
        minutes = date.minute + minutes

        hours = minutes // 60
        if hours:
            date = cls.add_hours(company, date, hours)

        minutes = minutes % 60

        date = datetime.datetime(date.year, date.month, date.day, date.hour,
                                 minutes, date.second)

        return date

    @classmethod
    def add_hours(cls, company, date, hours):
        while hours:
            if hours != intfloor(hours):
                minutes = (hours - intfloor(hours)) * 60
                date = cls.add_minutes(company, date, minutes)
            hours = intfloor(hours)

            hours = date.hour + hours
            days = hours // company.hours_per_work_day
            if days:
                date = cls.add_days(company, date, days)

            hours = hours % company.hours_per_work_day

            date = datetime.datetime(date.year, date.month, date.day,
                                     intfloor(hours), date.minute, date.second)

            hours = hours - intfloor(hours)

        return date

    @classmethod
    def add_days(cls, company, date, days):
        day_per_week = company.hours_per_work_week / company.hours_per_work_day

        while days:
            if days != intfloor(days):
                hours = (days - intfloor(days)) * company.hours_per_work_day
                date = cls.add_hours(company, date, hours)
            days = intfloor(days)

            days = date.weekday() + days

            weeks = days // day_per_week
            days = days % day_per_week

            if weeks:
                date = cls.add_weeks(company, date, weeks)

            date += datetime.timedelta(days=-date.weekday() + intfloor(days))

            days = days - intfloor(days)

        return date

    @classmethod
    def add_weeks(cls, company, date, weeks):
        day_per_week = company.hours_per_work_week / company.hours_per_work_day

        if weeks != intfloor(weeks):
            days = (weeks - intfloor(weeks)) * day_per_week
            if days:
                date = cls.add_days(company, date, days)

        date += datetime.timedelta(days=7 * intfloor(weeks))

        return date

    def compute_dates(self):
        values = {}
        get_early_finish = lambda work: values.get(work, {}).get(
            'early_finish_time', work.early_finish_time)
        get_late_start = lambda work: values.get(work, {}).get(
            'late_start_time', work.late_start_time)
        maxdate = lambda x, y: x and y and max(x, y) or x or y
        mindate = lambda x, y: x and y and min(x, y) or x or y

        # propagate constraint_start_time
        constraint_start = reduce(maxdate, (pred.early_finish_time
                                            for pred in self.predecessors),
                                  None)

        if constraint_start is None and self.parent:
            constraint_start = self.parent.early_start_time

        constraint_start = maxdate(constraint_start,
                                   self.constraint_start_time)

        works = deque([(self, constraint_start)])
        work2children = {}
        parent = None

        while works or parent:
            if parent:
                work = parent
                parent = None

                # Compute early_finish
                if work.children:
                    early_finish_time = reduce(
                        maxdate, map(get_early_finish, work.children), None)
                else:
                    early_finish_time = None
                    if values[work]['early_start_time']:
                        early_finish_time = self.add_hours(
                            work.company, values[work]['early_start_time'],
                            work.hours)
                values[work]['early_finish_time'] = early_finish_time

                # Propagate constraint_start on successors
                for w in work.successors:
                    works.append((w, early_finish_time))

                if not work.parent:
                    continue

                # housecleaning work2children
                if work.parent not in work2children:
                    work2children[work.parent] = set()
                work2children[work.parent].update(work.successors)

                if work in work2children[work.parent]:
                    work2children[work.parent].remove(work)

                # if no sibling continue to walk up the tree
                if not work2children.get(work.parent):
                    if work.parent not in values:
                        values[work.parent] = {}
                    parent = work.parent

                continue

            work, constraint_start = works.popleft()
            # take constraint define on the work into account
            constraint_start = maxdate(constraint_start,
                                       work.constraint_start_time)

            if constraint_start:
                early_start = self.add_hours(work.company, constraint_start,
                                             work.leveling_delay)
            else:
                early_start = None

            # update values
            if work not in values:
                values[work] = {}
            values[work]['early_start_time'] = early_start

            # Loop on children if they exist
            if work.children and work not in work2children:
                work2children[work] = set(work.children)
                # Propagate constraint_start on children
                for w in work.children:
                    if w.predecessors:
                        continue
                    works.append((w, early_start))
            else:
                parent = work

        # propagate constraint_finish_time
        constraint_finish = reduce(mindate, (succ.late_start_time
                                             for succ in self.successors),
                                   None)

        if constraint_finish is None and self.parent:
            constraint_finish = self.parent.late_finish_time

        constraint_finish = mindate(constraint_finish,
                                    self.constraint_finish_time)

        works = deque([(self, constraint_finish)])
        work2children = {}
        parent = None

        while works or parent:
            if parent:
                work = parent
                parent = None

                # Compute late_start
                if work.children:
                    reduce(mindate, map(get_late_start, work.children), None)
                else:
                    late_start_time = None
                    if values[work]['late_finish_time']:
                        late_start_time = self.add_hours(
                            work.company, values[work]['late_finish_time'],
                            -work.hours)
                values[work]['late_start_time'] = late_start_time

                # Propagate constraint_finish on predecessors
                for w in work.predecessors:
                    works.append((w, late_start_time))

                if not work.parent:
                    continue

                # housecleaning work2children
                if work.parent not in work2children:
                    work2children[work.parent] = set()
                work2children[work.parent].update(work.predecessors)

                if work in work2children[work.parent]:
                    work2children[work.parent].remove(work)

                # if no sibling continue to walk up the tree
                if not work2children.get(work.parent):
                    if work.parent not in values:
                        values[work.parent] = {}
                    parent = work.parent

                continue

            work, constraint_finish = works.popleft()
            # take constraint define on the work into account
            constraint_finish = mindate(constraint_finish,
                                        work.constraint_finish_time)

            if constraint_finish:
                late_finish = self.add_hours(work.company, constraint_finish,
                                             -work.back_leveling_delay)
            else:
                late_finish = None

            # update values
            if work not in values:
                values[work] = {}
            values[work]['late_finish_time'] = late_finish

            # Loop on children if they exist
            if work.children and work not in work2children:
                work2children[work] = set(work.children)
                # Propagate constraint_start on children
                for w in work.children:
                    if w.successors:
                        continue
                    works.append((w, late_finish))
            else:
                parent = work

        # write values
        write_fields = ('early_start_time', 'early_finish_time',
                        'late_start_time', 'late_finish_time')
        to_write = []
        for work, val in values.items():
            write_cond = False
            for field in write_fields:
                if field in val and getattr(work, field) != val[field]:
                    write_cond = True
                    break

            if write_cond:
                to_write.extend(([work], val))
        if to_write:
            self.write(*to_write)

    def reset_leveling(self):
        get_key = lambda w: (set(p.id for p in w.predecessors),
                             set(s.id for s in w.successors))

        if not self.parent:
            return
        siblings = self.search([('parent', '=', self.parent.id)])
        to_clean = []

        ref_key = get_key(self)
        for sibling in siblings:
            if sibling.leveling_delay == sibling.back_leveling_delay == 0:
                continue
            if get_key(sibling) == ref_key:
                to_clean.append(sibling)

        if to_clean:
            self.write(to_clean, {
                'leveling_delay': 0,
                'back_leveling_delay': 0,
            })

    def create_leveling(self):
        # define some helper functions
        get_key = lambda w: (set(p.id for p in w.predecessors),
                             set(s.id for s in w.successors))
        over_alloc = lambda current_alloc, work: (reduce(
            lambda res, alloc:
            (res or (current_alloc[alloc.employee.id] + alloc.percentage) > 100
             ), work.allocations, False))

        def sum_allocs(current_alloc, work):
            res = defaultdict(float)
            for alloc in work.allocations:
                empl = alloc.employee.id
                res[empl] = current_alloc[empl] + alloc.percentage
            return res

        def compute_delays(siblings):
            # time_line is a list [[end_delay, allocations], ...], this
            # mean that allocations is valid between the preceding end_delay
            # (or 0 if it doesn't exist) and the current end_delay.
            timeline = []
            for sibling in siblings:
                delay = 0
                ignored = []
                overloaded = []
                item = None

                while timeline:
                    # item is [end_delay, allocations]
                    item = heappop(timeline)
                    if over_alloc(item[1], sibling):
                        ignored.extend(overloaded)
                        ignored.append(item)
                        delay = item[0]
                        continue
                    elif item[1] >= delay + sibling.duration:
                        overloaded.append(item)
                    else:
                        # Succes!
                        break

                heappush(timeline, [
                    delay + sibling.duration,
                    sum_allocs(defaultdict(float), sibling), sibling.id
                ])

                for i in ignored:
                    heappush(timeline, i)
                for i in overloaded:
                    i[1] = sum_allocs(i[1], sibling)
                    heappush(timeline, i)

                yield sibling, delay

        siblings = self.search([('parent', '=',
                                 self.parent.id if self.parent else None)])

        refkey = get_key(self)
        siblings = [s for s in siblings if get_key(s) == refkey]

        for sibling, delay in compute_delays(siblings):
            sibling.leveling_delay = delay

        siblings.reverse()
        for sibling, delay in compute_delays(siblings):
            sibling.back_leveling_delay = delay
        self.__class__.save(siblings)

        if self.parent:
            self.parent.compute_dates()

    @classmethod
    def write(cls, *args):
        super(Work, cls).write(*args)

        actions = iter(args)
        for works, values in zip(actions, actions):
            if 'effort' in values:
                for work in works:
                    work.reset_leveling()
            fields = ('constraint_start_time', 'constraint_finish_time',
                      'effort')
            if reduce(lambda x, y: x or y in values, fields, False):
                for work in works:
                    work.compute_dates()

    @classmethod
    def create(cls, vlist):
        works = super(Work, cls).create(vlist)
        for work in works:
            work.reset_leveling()
            work.compute_dates()
        return works

    @classmethod
    def delete(cls, works):
        to_update = set()
        for work in works:
            if work.parent and work.parent not in works:
                to_update.add(work.parent)
                to_update.update(c for c in work.parent.children
                                 if c not in works)
        super(Work, cls).delete(works)

        for work in to_update:
            work.reset_leveling()
            work.compute_dates()
Ejemplo n.º 17
0
class Account(DeactivableMixin, tree('distribution_parents'), tree(), ModelSQL,
              ModelView):
    'Analytic Account'
    __name__ = 'analytic_account.account'
    name = fields.Char('Name', required=True, translate=True, select=True)
    code = fields.Char('Code', select=True)
    company = fields.Many2One('company.company', 'Company', required=True)
    currency = fields.Function(
        fields.Many2One('currency.currency', 'Currency'),
        'on_change_with_currency')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'on_change_with_currency_digits')
    type = fields.Selection([
        ('root', 'Root'),
        ('view', 'View'),
        ('normal', 'Normal'),
        ('distribution', 'Distribution'),
    ],
                            'Type',
                            required=True)
    root = fields.Many2One('analytic_account.account',
                           'Root',
                           select=True,
                           domain=[
                               ('company', '=', Eval('company', -1)),
                               ('parent', '=', None),
                               ('type', '=', 'root'),
                           ],
                           states={
                               'invisible': Eval('type') == 'root',
                               'required': Eval('type') != 'root',
                           },
                           depends=['company', 'type'])
    parent = fields.Many2One('analytic_account.account',
                             'Parent',
                             select=True,
                             domain=[
                                 'OR',
                                 ('root', '=', Eval('root', -1)),
                                 ('parent', '=', None),
                             ],
                             states={
                                 'invisible': Eval('type') == 'root',
                                 'required': Eval('type') != 'root',
                             },
                             depends=['root', 'type'])
    childs = fields.One2Many('analytic_account.account',
                             'parent',
                             'Children',
                             states={
                                 'invisible': Eval('id', -1) < 0,
                             },
                             domain=[
                                 ('company', '=', Eval('company', -1)),
                             ],
                             depends=['company'])
    balance = fields.Function(
        fields.Numeric('Balance',
                       digits=(16, Eval('currency_digits', 1)),
                       depends=['currency_digits']), 'get_balance')
    credit = fields.Function(
        fields.Numeric('Credit',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_credit_debit')
    debit = fields.Function(
        fields.Numeric('Debit',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_credit_debit')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('opened', 'Opened'),
        ('closed', 'Closed'),
    ],
                             'State',
                             required=True)
    note = fields.Text('Note')
    distributions = fields.One2Many('analytic_account.account.distribution',
                                    'parent',
                                    "Distributions",
                                    states={
                                        'invisible':
                                        Eval('type') != 'distribution',
                                        'required':
                                        Eval('type') == 'distribution',
                                    },
                                    depends=['type'])
    distribution_parents = fields.Many2Many(
        'analytic_account.account.distribution',
        'account',
        'parent',
        "Distribution Parents",
        readonly=True)

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

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

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

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

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

    @staticmethod
    def default_type():
        return 'normal'

    @staticmethod
    def default_state():
        return 'draft'

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def distribute(self, amount):
        "Return a list of (account, amount) distribution"
        assert self.type in {'normal', 'distribution'}
        if self.type == 'normal':
            return [(self, amount)]
        else:
            result = []
            remainder = amount
            for distribution in self.distributions:
                account = distribution.account
                ratio = distribution.ratio
                current_amount = self.currency.round(amount * ratio)
                remainder -= current_amount
                result.extend(account.distribute(current_amount))
            if remainder:
                i = 0
                while remainder:
                    account, amount = result[i]
                    rounding = self.currency.rounding.copy_sign(remainder)
                    result[i] = (account, amount - rounding)
                    remainder -= rounding
                    i = (i + 1) % len(result)
            return result
Ejemplo n.º 18
0
class Location(DeactivableMixin, tree(), ModelSQL, ModelView):
    "Stock Location"
    __name__ = 'stock.location'
    name = fields.Char("Name",
                       size=None,
                       required=True,
                       states=STATES,
                       depends=DEPENDS,
                       translate=True)
    code = fields.Char("Code",
                       size=None,
                       states=STATES,
                       depends=DEPENDS,
                       select=True)
    address = fields.Many2One("party.address",
                              "Address",
                              states={
                                  'invisible': Eval('type') != 'warehouse',
                                  'readonly': ~Eval('active'),
                              },
                              depends=['type', 'active'])
    type = fields.Selection([
        ('supplier', 'Supplier'),
        ('customer', 'Customer'),
        ('lost_found', 'Lost and Found'),
        ('warehouse', 'Warehouse'),
        ('storage', 'Storage'),
        ('production', 'Production'),
        ('drop', 'Drop'),
        ('view', 'View'),
    ],
                            'Location type',
                            states=STATES,
                            depends=DEPENDS)
    parent = fields.Many2One("stock.location",
                             "Parent",
                             select=True,
                             left="left",
                             right="right",
                             states={
                                 'invisible': Eval('type') == 'warehouse',
                             },
                             depends=['type'])
    left = fields.Integer('Left', required=True, select=True)
    right = fields.Integer('Right', required=True, select=True)
    childs = fields.One2Many("stock.location", "parent", "Children")
    flat_childs = fields.Boolean(
        "Flat Children", help="Check to restrict to one level of children.")
    warehouse = fields.Function(fields.Many2One('stock.location', 'Warehouse'),
                                'get_warehouse')
    input_location = fields.Many2One("stock.location",
                                     "Input",
                                     states={
                                         'invisible':
                                         Eval('type') != 'warehouse',
                                         'readonly': ~Eval('active'),
                                         'required':
                                         Eval('type') == 'warehouse',
                                     },
                                     domain=[
                                         ('type', '=', 'storage'),
                                         [
                                             'OR',
                                             ('parent', 'child_of',
                                              [Eval('id')]),
                                             ('parent', '=', None),
                                         ],
                                     ],
                                     depends=['type', 'active', 'id'])
    output_location = fields.Many2One("stock.location",
                                      "Output",
                                      states={
                                          'invisible':
                                          Eval('type') != 'warehouse',
                                          'readonly': ~Eval('active'),
                                          'required':
                                          Eval('type') == 'warehouse',
                                      },
                                      domain=[('type', '=', 'storage'),
                                              [
                                                  'OR',
                                                  ('parent', 'child_of',
                                                   [Eval('id')]),
                                                  ('parent', '=', None)
                                              ]],
                                      depends=['type', 'active', 'id'])
    storage_location = fields.Many2One(
        "stock.location",
        "Storage",
        states={
            'invisible': Eval('type') != 'warehouse',
            'readonly': ~Eval('active'),
            'required': Eval('type') == 'warehouse',
        },
        domain=[('type', 'in', ['storage', 'view']),
                [
                    'OR', ('parent', 'child_of', [Eval('id')]),
                    ('parent', '=', None)
                ]],
        depends=['type', 'active', 'id'])
    picking_location = fields.Many2One(
        'stock.location',
        'Picking',
        states={
            'invisible': Eval('type') != 'warehouse',
            'readonly': ~Eval('active'),
        },
        domain=[
            ('type', '=', 'storage'),
            ('parent', 'child_of', [Eval('storage_location', -1)]),
        ],
        depends=['type', 'active', 'storage_location'],
        help='If empty the Storage is used')
    quantity = fields.Function(fields.Float('Quantity'),
                               'get_quantity',
                               searcher='search_quantity')
    forecast_quantity = fields.Function(fields.Float('Forecast Quantity'),
                                        'get_quantity',
                                        searcher='search_quantity')
    cost_value = fields.Function(fields.Numeric('Cost Value'),
                                 'get_cost_value')

    @classmethod
    def __setup__(cls):
        super(Location, cls).__setup__()
        cls._order.insert(0, ('name', 'ASC'))
        cls._error_messages.update({
            'invalid_type_for_moves':
            ('Location "%s" with existing moves '
             'cannot be changed to a type that does not support moves.'),
            'child_of_warehouse': ('Location "%(location)s" must be a '
                                   'child of warehouse "%(warehouse)s".'),
            'inactive_location_with_moves':
            ("The location '%(location)s' must be empty "
             "to be deactivated."),
        })

        parent_domain = [[
            'OR',
            ('parent.flat_childs', '=', False),
            ('parent', '=', None),
        ]]
        childs_domain = [
            If(Eval('flat_childs', False), ('childs', '=', None), ()),
        ]
        childs_mapping = cls._childs_domain()
        for type_, allowed_parents in cls._parent_domain().items():
            parent_domain.append(
                If(Eval('type') == type_, ('type', 'in', allowed_parents), ()))
            childs_domain.append(
                If(
                    Eval('type') == type_,
                    ('type', 'in', childs_mapping[type_]), ()))
        cls.parent.domain = parent_domain
        cls.childs.domain = childs_domain
        cls.childs.depends.extend(['flat_childs', 'type'])

    @classmethod
    def _parent_domain(cls):
        '''Returns a dict with location types as keys and a list of allowed
        parent location types as values'''
        return {
            'customer': ['customer'],
            'supplier': ['supplier'],
            'production': ['production'],
            'lost_found': ['lost_found'],
            'view': ['warehouse', 'view', 'storage'],
            'storage': ['warehouse', 'view', 'storage'],
            'warehouse': [''],
        }

    @classmethod
    def _childs_domain(cls):
        childs_domain = {}
        for type_, allowed_parents in cls._parent_domain().items():
            for parent in allowed_parents:
                childs_domain.setdefault(parent, [])
                childs_domain[parent].append(type_)
        return childs_domain

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

        table = cls.__table_handler__(module_name)
        table.index_action(['left', 'right'], 'add')

    @classmethod
    def validate(cls, locations):
        super(Location, cls).validate(locations)
        inactives = []
        for location in locations:
            location.check_type_for_moves()
            if not location.active:
                inactives.append(location)
        cls.check_inactive(inactives)

    def check_type_for_moves(self):
        """ Check locations with moves have types compatible with moves. """
        invalid_move_types = ['warehouse', 'view']
        Move = Pool().get('stock.move')
        if self.type in invalid_move_types:
            # Use root to compute for all companies
            with Transaction().set_user(0):
                moves = Move.search([
                    [
                        'OR',
                        ('to_location', '=', self.id),
                        ('from_location', '=', self.id),
                    ],
                    ('state', 'not in', ['staging', 'draft']),
                ])
            if moves:
                self.raise_user_error('invalid_type_for_moves',
                                      (self.rec_name, ))

    @classmethod
    def check_inactive(cls, locations):
        "Check inactive location are empty"
        assert all(not l.active for l in locations)
        empty = cls.get_empty_locations(locations)
        non_empty = set(locations) - set(empty)
        if non_empty:
            cls.raise_user_error('inactive_location_with_moves', {
                'location': next(iter(non_empty)).rec_name,
            })

    @classmethod
    def get_empty_locations(cls, locations=None):
        pool = Pool()
        Move = pool.get('stock.move')
        if locations is None:
            locations = cls.search([])
        if not locations:
            return []
        location_ids = list(map(int, locations))
        # Use root to compute for all companies
        # and ensures inactive locations are in the query
        with Transaction().set_user(0), \
                Transaction().set_context(active_test=False):
            query = Move.compute_quantities_query(location_ids,
                                                  with_childs=True)
            quantities = Move.compute_quantities(query,
                                                 location_ids,
                                                 with_childs=True)
            empty = set(location_ids)
            for (location_id, product), quantity in quantities.items():
                if quantity:
                    empty.discard(location_id)
            for sub_ids in grouped_slice(list(empty)):
                sub_ids = list(sub_ids)
                moves = Move.search([
                    ('state', 'not in', ['done', 'cancel']),
                    [
                        'OR',
                        ('from_location', 'in', sub_ids),
                        ('to_location', 'in', sub_ids),
                    ],
                ])
                for move in moves:
                    for location in [move.from_location, move.to_location]:
                        empty.discard(location.id)
        return cls.browse(empty)

    @staticmethod
    def default_left():
        return 0

    @staticmethod
    def default_right():
        return 0

    @classmethod
    def default_flat_childs(cls):
        return False

    @staticmethod
    def default_type():
        return 'storage'

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

    def get_warehouse(self, name):
        # Order by descending left to get the first one in the tree
        with Transaction().set_context(active_test=False):
            locations = self.search([
                ('parent', 'parent_of', [self.id]),
                ('type', '=', 'warehouse'),
            ],
                                    order=[('left', 'DESC')])
        if locations:
            return locations[0].id

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

    @classmethod
    def get_quantity(cls, locations, name):
        pool = Pool()
        Product = pool.get('product.product')
        Date_ = pool.get('ir.date')
        trans_context = Transaction().context

        def valid_context(name):
            return (trans_context.get(name) is not None
                    and isinstance(trans_context[name], int))

        if not any(map(valid_context, ['product', 'product_template'])):
            return {l.id: None for l in locations}

        context = {}
        if (name == 'quantity' and (trans_context.get(
                'stock_date_end', datetime.date.max) > Date_.today())):
            context['stock_date_end'] = Date_.today()

        if name == 'forecast_quantity':
            context['forecast'] = True
            if not trans_context.get('stock_date_end'):
                context['stock_date_end'] = datetime.date.max

        if trans_context.get('product') is not None:
            grouping = ('product', )
            grouping_filter = ([trans_context['product']], )
            key = trans_context['product']
        else:
            grouping = ('product.template', )
            grouping_filter = ([trans_context['product_template']], )
            key = trans_context['product_template']
        pbl = {}
        for sub_locations in grouped_slice(locations):
            location_ids = [l.id for l in sub_locations]
            with Transaction().set_context(context):
                pbl.update(
                    Product.products_by_location(
                        location_ids,
                        grouping=grouping,
                        grouping_filter=grouping_filter,
                        with_childs=trans_context.get('with_childs', True)))

        return dict((loc.id, pbl.get((loc.id, key), 0)) for loc in locations)

    @classmethod
    def search_quantity(cls, name, domain):
        _, operator_, operand = domain
        operator_ = {
            '=': operator.eq,
            '>=': operator.ge,
            '>': operator.gt,
            '<=': operator.le,
            '<': operator.lt,
            '!=': operator.ne,
            'in': lambda v, l: v in l,
            'not in': lambda v, l: v not in l,
        }.get(operator_, lambda v, l: False)

        ids = []
        for location in cls.search([]):
            if operator_(getattr(location, name), operand):
                ids.append(location.id)
        return [('id', 'in', ids)]

    @classmethod
    def get_cost_value(cls, locations, name):
        pool = Pool()
        Product = pool.get('product.product')
        Template = pool.get('product.template')
        trans_context = Transaction().context
        cost_values = {l.id: None for l in locations}

        def valid_context(name):
            return (trans_context.get(name) is not None
                    and isinstance(trans_context[name], int))

        if not any(map(valid_context, ['product', 'product_template'])):
            return cost_values

        def get_record():
            if trans_context.get('product') is not None:
                return Product(trans_context['product'])
            else:
                return Template(trans_context['product_template'])

        context = {}
        if 'stock_date_end' in trans_context:
            # Use the last cost_price of the day
            context['_datetime'] = datetime.datetime.combine(
                trans_context['stock_date_end'], datetime.time.max)
            # The date could be before the product creation
            record = get_record()
            if record.create_date > context['_datetime']:
                return cost_values
        with Transaction().set_context(context):
            cost_price = get_record().cost_price
        # The template may have more than one product
        if cost_price is not None:
            for location in locations:
                cost_values[location.id] = (Decimal(str(location.quantity)) *
                                            cost_price)
        return cost_values

    @classmethod
    def _set_warehouse_parent(cls, locations):
        '''
        Set the parent of child location of warehouse if not set
        '''
        to_update = set()
        to_save = []
        for location in locations:
            if location.type == 'warehouse':
                if not location.input_location.parent:
                    to_update.add(location.input_location)
                if not location.output_location.parent:
                    to_update.add(location.output_location)
                if not location.storage_location.parent:
                    to_update.add(location.storage_location)
                if to_update:
                    for child_location in to_update:
                        child_location.parent = location
                        to_save.append(child_location)
                    to_update.clear()
        cls.save(to_save)

    @classmethod
    def create(cls, vlist):
        locations = super(Location, cls).create(vlist)
        cls._set_warehouse_parent(locations)
        return locations

    @classmethod
    def write(cls, *args):
        super(Location, cls).write(*args)
        locations = sum(args[::2], [])
        cls._set_warehouse_parent(locations)

        ids = [l.id for l in locations]
        warehouses = cls.search([('type', '=', 'warehouse'),
                                 [
                                     'OR',
                                     ('storage_location', 'in', ids),
                                     ('input_location', 'in', ids),
                                     ('output_location', 'in', ids),
                                 ]])

        fields = ('storage_location', 'input_location', 'output_location')
        wh2childs = {}
        for warehouse in warehouses:
            in_out_sto = (getattr(warehouse, f).id for f in fields)
            for location in locations:
                if location.id not in in_out_sto:
                    continue
                childs = wh2childs.setdefault(
                    warehouse.id,
                    cls.search([
                        ('parent', 'child_of', warehouse.id),
                    ]))
                if location not in childs:
                    cls.raise_user_error(
                        'child_of_warehouse', {
                            'location': location.rec_name,
                            'warehouse': warehouse.rec_name,
                        })

    @classmethod
    def copy(cls, locations, default=None):
        if default is None:
            default = {}
        else:
            default = default.copy()

        res = []
        for location in locations:
            if location.type == 'warehouse':

                wh_default = default.copy()
                wh_default['type'] = 'view'
                wh_default['input_location'] = None
                wh_default['output_location'] = None
                wh_default['storage_location'] = None
                wh_default['childs'] = None

                new_location, = super(Location, cls).copy([location],
                                                          default=wh_default)

                with Transaction().set_context(
                        cp_warehouse_locations={
                            'input_location': location.input_location.id,
                            'output_location': location.output_location.id,
                            'storage_location': location.storage_location.id,
                        },
                        cp_warehouse_id=new_location.id):
                    cls.copy(location.childs,
                             default={'parent': new_location.id})
                cls.write([new_location], {
                    'type': 'warehouse',
                })
            else:
                new_location, = super(Location, cls).copy([location],
                                                          default=default)
                warehouse_locations = Transaction().context.get(
                    'cp_warehouse_locations') or {}
                if location.id in warehouse_locations.values():
                    cp_warehouse = cls(
                        Transaction().context['cp_warehouse_id'])
                    for field, loc_id in warehouse_locations.items():
                        if loc_id == location.id:
                            cls.write([cp_warehouse], {
                                field: new_location.id,
                            })

            res.append(new_location)
        return res
Ejemplo n.º 19
0
class Type(sequence_ordered(), ModelSQL, ModelView, tree(separator='\\')):
    'Account Meta Type'
    __name__ = 'account.account.meta.type'

    _states = {
        'readonly':
        (Bool(Eval('template', -1)) & ~Eval('template_override', False)),
    }
    name = fields.Char('Name', size=None, required=True, states=_states)
    parent = fields.Many2One('account.account.meta.type',
                             'Parent',
                             ondelete="RESTRICT",
                             states=_states,
                             domain=[
                                 ('company', '=', Eval('company')),
                             ],
                             depends=['company'])
    childs = fields.One2Many('account.account.meta.type',
                             'parent',
                             'Children',
                             domain=[
                                 ('company', '=', Eval('company')),
                             ],
                             depends=['company'])
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
                                      'get_currency_digits')
    amount = fields.Function(
        fields.Numeric('Amount',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_amount')
    amount_cmp = fields.Function(
        fields.Numeric('Amount',
                       digits=(16, Eval('currency_digits', 2)),
                       depends=['currency_digits']), 'get_amount_cmp')
    balance_sheet = fields.Boolean('Balance Sheet', states=_states)
    income_statement = fields.Boolean('Income Statement', states=_states)
    display_balance = fields.Selection([
        ('debit-credit', 'Debit - Credit'),
        ('credit-debit', 'Credit - Debit'),
    ],
                                       'Display Balance',
                                       required=True,
                                       states=_states)
    company = fields.Many2One('company.company',
                              'Company',
                              required=True,
                              ondelete="RESTRICT")
    template = fields.Many2One('account.account.meta.type.template',
                               'Template')
    template_override = fields.Boolean(
        'Override Template',
        help="Check to override template definition",
        states={
            'invisible': ~Bool(Eval('template', -1)),
        },
        depends=['template'])
    level = fields.Function(fields.Numeric('Level', digits=(2, 0)),
                            '_get_level')
    type_display_balance = fields.Selection([('debit', 'Debit'),
                                             ('credit', 'Credit')], 'Type')
    custom_amount = fields.Function(
        fields.Numeric('Custom Amount', digits=(2, 0)), '_get_custom_amount')

    del _states

    def _get_level(self, parent=None):
        level = 0
        if self.parent:
            level = self.parent.level + 1
        return level

    def _get_custom_amount(self, name):
        amount = 0
        if self.type_display_balance == 'credit':
            amount = -self.amount
        else:
            amount = self.amount
        return amount

    def _get_childs_by_order(self, res=None):
        '''Returns the records of all the children computed recursively, and sorted by sequence. Ready for the printing'''

        Account = Pool().get('account.account.meta.type')

        if res is None:
            res = []

        childs = Account.search([('parent', '=', self.id)],
                                order=[('sequence', 'ASC')])

        if len(childs) >= 1:
            for child in childs:
                res.append(Account(child.id))
                child._get_childs_by_order(res=res)
        return res

    #@classmethod
    #def validate(cls, types):
    #    super(Type, cls).validate(types)
    #    cls.check_recursion(types, rec_name='name')

    @staticmethod
    def default_balance_sheet():
        return False

    @staticmethod
    def default_income_statement():
        return False

    @staticmethod
    def default_display_balance():
        return 'debit-credit'

    @staticmethod
    def default_type_display_balance():
        return 'debit'

    @classmethod
    def default_template_override(cls):
        return False

    def get_currency_digits(self, name):
        return self.company.currency.digits

    @classmethod
    def get_amount(cls, types, name):
        pool = Pool()
        Account = pool.get('account.account')
        GeneralLedger = pool.get('account.general_ledger.account')
        transaction = Transaction()
        context = transaction.context

        res = {}
        for type_ in types:
            res[type_.id] = Decimal('0.0')

        childs = cls.search([
            ('parent', 'child_of', [t.id for t in types]),
        ])
        type_sum = {}
        for type_ in childs:
            type_sum[type_.id] = Decimal('0.0')

        start_period_ids = GeneralLedger.get_period_ids('start_%s' % name)
        end_period_ids = GeneralLedger.get_period_ids('end_%s' % name)
        period_ids = list(
            set(end_period_ids).difference(set(start_period_ids)))

        for company in context.get('companies', []):
            with transaction.set_context(company=company['id'],
                                         posted=True,
                                         cumulate=True):
                accounts = Account.search([
                    ('company', '=', company['id']),
                    ('type.meta_type', 'in', [t.id for t in childs]),
                    ('kind', '!=', 'view'),
                ])
                for account in accounts:
                    key = account.type.meta_type.id
                    type_sum[key] += (account.debit - account.credit)

        for type_ in types:
            childs = cls.search([
                ('parent', 'child_of', [type_.id]),
            ])
            for child in childs:
                res[type_.id] += type_sum[child.id]
            exp = Decimal(str(10.0**-type_.currency_digits))
            res[type_.id] = res[type_.id].quantize(exp)
            if type_.display_balance == 'credit-debit':
                res[type_.id] = -res[type_.id]
        return res

    @classmethod
    def get_amount_cmp(cls, types, name):
        transaction = Transaction()
        current = transaction.context
        if not current.get('comparison'):
            return dict.fromkeys([t.id for t in types], None)
        new = {}
        for key, value in current.iteritems():
            if key.endswith('_cmp'):
                new[key[:-4]] = value
        with transaction.set_context(new):
            return cls.get_amount(types, name)

    @classmethod
    def view_attributes(cls):
        return [
            ('/tree/field[@name="amount_cmp"]', 'tree_invisible',
             ~Eval('comparison', False)),
        ]

    def get_rec_name(self, name):
        #if self.parent:
        #    return self.parent.get_rec_name(name) + '\\' + self.name
        #else:
        #    return self.name
        return self.name

    @classmethod
    def delete(cls, types):
        types = cls.search([
            ('parent', 'child_of', [t.id for t in types]),
        ])
        super(Type, cls).delete(types)

    def update_type(self, template2type=None):
        '''
        Update recursively types based on template.
        template2type is a dictionary with template id as key and type id as
        value, used to convert template id into type. The dictionary is filled
        with new types
        '''
        if template2type is None:
            template2type = {}

        values = []
        childs = [self]
        while childs:
            for child in childs:
                if child.template and not child.template_override:
                    vals = child.template._get_type_value(type=child)
                    if vals:
                        values.append([child])
                        values.append(vals)
                    template2type[child.template.id] = child.id
            childs = sum((c.childs for c in childs), ())
        if values:
            self.write(*values)
Ejemplo n.º 20
0
class TypeTemplate(tree(separator='\\'), sequence_ordered(), ModelSQL,
                   ModelView):
    'Account Meta Type Template'
    __name__ = 'account.account.meta.type.template'
    name = fields.Char('Name', required=True)
    parent = fields.Many2One('account.account.meta.type.template',
                             'Parent',
                             ondelete="RESTRICT")
    childs = fields.One2Many('account.account.meta.type.template', 'parent',
                             'Children')
    balance_sheet = fields.Boolean('Balance Sheet')
    income_statement = fields.Boolean('Income Statement')
    display_balance = fields.Selection([
        ('debit-credit', 'Debit - Credit'),
        ('credit-debit', 'Credit - Debit'),
    ],
                                       'Display Balance',
                                       required=True)
    type_display_balance = fields.Selection([('debit', 'Debit'),
                                             ('credit', 'Credit')], 'Type')

    #@classmethod
    #def validate(cls, records):
    #    super(TypeTemplate, cls).validate(records)
    #    cls.check_recursion(records, rec_name='name')

    @staticmethod
    def default_balance_sheet():
        return False

    @staticmethod
    def default_income_statement():
        return False

    @staticmethod
    def default_display_balance():
        return 'debit-credit'

    @staticmethod
    def default_type_display_balance():
        return 'debit'

    def get_rec_name(self, name):
        if self.parent:
            return self.parent.get_rec_name(name) + '\\' + self.name
        else:
            return self.name

    def _get_type_value(self, type=None):
        '''
        Set the values for account creation.
        '''
        res = {}
        if not type or type.name != self.name:
            res['name'] = self.name
        if not type or type.sequence != self.sequence:
            res['sequence'] = self.sequence
        if not type or type.balance_sheet != self.balance_sheet:
            res['balance_sheet'] = self.balance_sheet
        if not type or type.income_statement != self.income_statement:
            res['income_statement'] = self.income_statement
        if not type or type.display_balance != self.display_balance:
            res['display_balance'] = self.display_balance
        if not type or type.type_display_balance != self.type_display_balance:
            res['type_display_balance'] = self.type_display_balance
        if not type or type.template != self:
            res['template'] = self.id
        return res

    def create_type(self, company_id, template2type=None):
        '''
        Create recursively types based on template.
        template2type is a dictionary with template id as key and type id as
        value, used to convert template id into type. The dictionary is filled
        with new types.
        '''
        pool = Pool()
        Type = pool.get('account.account.meta.type')
        assert self.parent is None

        if template2type is None:
            template2type = {}

        def create(templates):
            values = []
            created = []
            for template in templates:
                if template.id not in template2type:
                    vals = template._get_type_value()
                    vals['company'] = company_id
                    if template.parent:
                        vals['parent'] = template2type[template.parent.id]
                    else:
                        vals['parent'] = None
                    values.append(vals)
                    created.append(template)

            types = Type.create(values)
            for template, type_ in zip(created, types):
                template2type[template.id] = type_.id

        childs = [self]
        while childs:
            create(childs)
            childs = sum((c.childs for c in childs), ())
Ejemplo n.º 21
0
class UIMenu(DeactivableMixin, sequence_ordered(), 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'

    @staticmethod
    def default_sequence():
        return 10

    @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), [])
        key = lambda k: k.action.type
        action_keywords.sort(key=key)
        for type, action_keywords in groupby(action_keywords, key=key):
            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
Ejemplo n.º 22
0
class TmiMetaGroup( 
        tree(separator='\\'), sequence_ordered(), ModelSQL, ModelView):
    'Meta Group'
    __name__ = 'tmi.meta.group'

    name = fields.Char('Name', size=None, required=True)
    code = fields.Char('Code')
    active = fields.Boolean('Active')
    company = fields.Many2One('company.company', 'Company', required=True)
    currency = fields.Function(fields.Many2One('currency.currency',
        'Currency'), 'get_currency')
    currency_digits = fields.Function(fields.Integer('Currency Digits'),
            'get_currency_digits')
    parent = fields.Many2One('tmi.meta.group', 'Parent',
        ondelete="RESTRICT",
        domain=[
            'OR',[ ('company','=',Eval('company',-1)), 
                ('company', 'in',Eval('company.childs',[])),
                ],
            ('type', '=', Eval('parent_type',None)),

            ],
        depends=['type','company'])
    childs = fields.One2Many('tmi.meta.group', 'parent', 'Children',
        domain=[
            'OR',[ ('company','=',Eval('company',-1)),
            ('company', 'in',Eval('company.childs',[])),
            ],
        ],
        depends=['company'])
    baptism = fields.Function(fields.Numeric('Baptism',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    small_group = fields.Function(fields.Numeric('Small Group',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    tithe = fields.Function(fields.Numeric('Tithe',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    offering = fields.Function(fields.Numeric('Offering',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    praise_thanksgiving = fields.Function(fields.Numeric('Praise and Thanksgiving', 
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    gathering = fields.Function(fields.Numeric('Gathering',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    church_planting = fields.Function(fields.Numeric('Church Planting',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    organizing_church = fields.Function(fields.Numeric('Organizing Church',
        digits=(16,Eval('currency_digits',2))), 'get_balance')
    type = fields.Selection( 
        [
        ('conference','Conference'),
        ('division','Division'),
        ('union','Union'),
        ('field','Field'),
        ('zone','Zone'),
        ('district','District'),
        ('church','Church'),
        ('small_group','Small Group'),
        ]
        ,'Type', 
        required=True,
        sort=False)
    parent_type = fields.Function(fields.Selection(
        [
        ('conference','Conference'),
        ('division','Division'),
        ('union','Union'),
        ('field','Field'),
        ('zone','Zone'),
        ('district','District'),
        ('church','Church'),
        ('small_group','Small Group'),
        ],
        'Parent Type'),
        'get_parent_type')
    company = fields.Many2One('company.company', 'Company', required=True,
            ondelete="RESTRICT")
    child_value = fields.Function(fields.Numeric('Child Value'),
        'get_child_value')

    tmi_baptism_target = fields.Function(fields.Numeric('Baptism Target',
        digits=(16,0 )), 'get_target')
    tmi_small_group_target = fields.Function(fields.Numeric('Small Group Target',
        digits=(16, 0)), 'get_target')
    tmi_tithe_target = fields.Function(fields.Numeric('Tithe  Target',
        digits=(16,Eval('currency_digits',2))), 'get_target')
    tmi_offering_target = fields.Function(fields.Numeric('Offering Target',
        digits=(16,Eval('currency_digits',2))), 'get_target')
    tmi_praise_thanksgiving_target = fields.Function(fields.Numeric('Praise and Thanksgiving Target', 
        digits=(16,Eval('currency_digits',2))), 'get_target')
    tmi_gathering_target = fields.Function(fields.Numeric('Gathering Target',
        digits=(16,Eval('currency_digits',2))), 'get_target')
    tmi_church_planting_target = fields.Function(fields.Numeric('Church Planting Target',
        digits=(16,0)), 'get_target')
    tmi_organizing_church_target = fields.Function(fields.Numeric('Organizing Church Target',
        digits=(16,0)), 'get_target')

    tmi_baptism_difference = fields.Function(fields.Numeric('Baptism Difference',
        digits=(16,0 )), 'get_difference')
    tmi_small_group_difference = fields.Function(fields.Numeric('Small Group Difference',
        digits=(16, 0)), 'get_difference')
    tmi_tithe_difference = fields.Function(fields.Numeric('Tithe  Difference',
        digits=(16,Eval('currency_digits',2))), 'get_difference')
    tmi_offering_difference = fields.Function(fields.Numeric('Offering Difference',
        digits=(16,Eval('currency_digits',2))), 'get_difference')
    tmi_praise_thanksgiving_difference = fields.Function(fields.Numeric('Praise and Thanksgiving Difference', 
        digits=(16,Eval('currency_digits',2))), 'get_difference')
    tmi_gathering_difference = fields.Function(fields.Numeric('Gathering Difference',
        digits=(16,Eval('currency_digits',2))), 'get_difference')
    tmi_church_planting_difference = fields.Function(fields.Numeric('Church Planting Difference',
        digits=(16,0)), 'get_difference')
    tmi_organizing_church_difference = fields.Function(fields.Numeric('Organizing Church Difference',
        digits=(16,0)), 'get_difference')

    tmi_baptism_percentage = fields.Function(fields.Numeric('Baptism Percentage',
        digits=(16,2)), 'get_percentage')
    tmi_small_group_percentage = fields.Function(fields.Numeric('Small Group Percentage',
        digits=(16,2)), 'get_percentage')
    tmi_tithe_percentage = fields.Function(fields.Numeric('Tithe  Percentage',
        digits=(16,2)), 'get_percentage')
    tmi_offering_percentage = fields.Function(fields.Numeric('Offering Percentage',
        digits=(16,2)), 'get_percentage')
    tmi_praise_thanksgiving_percentage = fields.Function(fields.Numeric('Praise and Thanksgiving Percentage', 
        digits=(16,2)), 'get_percentage')
    tmi_gathering_percentage = fields.Function(fields.Numeric('Gathering Percentage',
        digits=(16,2)), 'get_percentage')
    tmi_church_planting_percentage = fields.Function(fields.Numeric('Church Planting Percentage',
        digits=(16,2)), 'get_percentage')
    tmi_organizing_church_percentage = fields.Function(fields.Numeric('Organizing Church Percentage',
        digits=(16,2)), 'get_percentage')

    church = fields.Function(fields.Many2One('tmi.meta.group', 'Church',
        domain=[
            'OR',[ ('company','=',Eval('company',-1)), 
                ('company', 'in',Eval('company.childs',[])),
                ],
            ('type', '=', 'church'),

            ],
        depends=['type','company']),
        'get_church', 
        searcher='search_church',
    )

    district = fields.Function(fields.Many2One('tmi.meta.group', 'District',
        domain=[
            'OR',[ ('company','=',Eval('company',-1)), 
                ('company', 'in',Eval('company.childs',[])),
                ],
            ('type', '=', 'district'),

            ],
        depends=['type','company']),
        'get_district',
        searcher='search_district',
    )

    level = fields.Function(fields.Numeric('Level',digits=(2,0)),
        '_get_level')

    @classmethod
    def __setup__(cls):
        super(TmiMetaGroup, cls).__setup__()
        #cls._order.insert(0, ('baptism', 'ASC')) 
        cls._order.insert(0, ('name', 'ASC'))
        cls._order.insert(0, ('code', 'ASC'))

    def get_parent_type(self, name):
        if self.type=='small_group': 
            return 'church'
        if self.type=='church':
            return 'district'
        if self.type=='district':
            return 'zone'
        if self.type=='zone':
            return 'field'
        if self.type=='field':
            return 'union'
        if self.type=='union':
            return 'division'
        if self.type=='division':
            return 'conference'
        return None

    def _get_level(self, parent=None): 
        level = 0
        if self.parent:
            level = self.parent.level + 1
        return  level

    def _get_childs_by_order(self, res=None, _order=None):
        '''Returns the records of all the children computed recursively, and sorted by sequence. Ready for the printing'''
        
        Group = Pool().get('tmi.meta.group')
        
        if res is None: 
            res = []

        if _order is None: 
            _order = 'baptism'

        childs = Group.search([('parent', '=', self.id)])
        
        if len(childs)>=1:
            for child in childs:
                res.append(Group(child.id))
                child._get_childs_by_order(res=res)
        return res 

    @fields.depends('type')
    def get_church(self, name=None): 
        if self.type == 'small_group' and self.parent: 
            return self.parent.id
        return None 
    
    @fields.depends('type')
    def get_district(self, name=None): 
        if self.type == 'small_group' and self.parent:
            if self.parent.parent: 
                return self.parent.parent.id
        if self.type == 'church' and self.parent: 
            return self.parent.id 
        return None 
    

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

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

    @fields.depends('type','parent_type')
    def on_change_type(self):
        if self.type=='small_group':
            self.parent_type = 'church'
        if self.type=='church':
            self.parent_type = 'district'
        if self.type=='district':
            self.parent_type = 'zone'
        if self.type=='zone':
            self.parent_type = 'field'
        if self.type=='field':
            self.parent_type = 'union'
        if self.type=='union':
            self.parent_type = 'division'
        if self.type=='division':
            self.parent_type = 'conference'
        return None

    def get_type(self,value=None): 
        if value=='small_group': 
            return 'Small Group'
        if value=='church':
            return 'Church'
        if value=='district':
            return 'District'
        if value=='zone':
            return 'Zone'
        if value=='field':
            return 'Field'
        if value=='union':
            return 'Union'
        if value=='division':
            return 'Division'
        if value=='conference':
            return 'Conference'
        return None

    def get_rec_name(self, name):
        if self.code and self.parent:
            return self.name + ' - ' + self.code + ' - ' + self.parent.name
        elif self.code and not self.parent:
            return self.name + ' - ' + self.code
        elif self.parent and not self.code:
            return self.name + ' - ' + self.parent.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'
        return [bool_op,
            ('code',) + tuple(clause[1:]),
            ('type',) + tuple(clause[1:]),
            ('name',) + tuple(clause[1:]),
            (cls._rec_name,) + tuple(clause[1:]),
            ]

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

    def get_currency_digits(self, name): 
        return self.company.currency.digits

    def get_child_value(self, name): 
        if self.childs is not None and self.type !='small_group': 
            return sum(x.child_value for x in self.childs)
        elif self.childs is None and self.type =='small_group': 
            return 1 
        else: 
            children_sum = 1
            for child in self.childs:
                children_sum = sum(x.child_value for x in self.childs if x.type=='small_group')
            return children_sum 
        return 0 

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

    @staticmethod
    def default_active():
        return True 

    @staticmethod
    def default_type():
        return 'small_group'

    @classmethod
    def get_baptism(cls, metas, name):
        pool = Pool()
        Group = pool.get('tmi.group')
        Period = pool.get('tmi.period')

        res = {}
        for meta in metas:
            res[meta.id] = Decimal('0.0')

        childs = cls.search([
                ('parent', 'child_of', [m.id for m in metas]),
                ])
        meta_sum = {}
        for meta in childs:
            meta_sum[meta.id] = Decimal('0.0')

        start_period_ids = Period.get_period_ids('start_%s' % name)
        end_period_ids = Period.get_period_ids('end_%s' % name)
        period_ids = list(
            set(end_period_ids).difference(set(start_period_ids)))
        with Transaction().set_context(periods=period_ids):
            groups = Group.search([
                    ('meta', 'in', [m.id for m in childs]),
                    ])
        
        for group in groups:
            meta_sum[group.meta.id] += (group.baptism)

        for meta in metas:
            childs = cls.search([
                    ('parent', 'child_of', [meta.id]),
                    ])
            for child in childs:
                res[meta.id] += meta_sum[child.id]
            exp = Decimal(str(10.0 ** -meta.currency_digits))
            res[meta.id] = res[meta.id].quantize(exp)
        return res

    @classmethod
    def get_balance(cls, metas, name):
        pool = Pool()
        Group = pool.get('tmi.group')
        Period = pool.get('tmi.period')

        res = {}
        for meta in metas:
            res[meta.id] = Decimal('0.0')

        childs = cls.search([
                ('parent', 'child_of', [m.id for m in metas]),
                ])

        meta_sum = {}
        for meta in childs:
            meta_sum[meta.id] = Decimal('0.0')

        start_period_ids = Period.get_period_ids('start_%s' % name)
        end_period_ids = Period.get_period_ids('end_%s' % name)
        period_ids = list(
            set(end_period_ids).difference(set(start_period_ids)))

        with Transaction().set_context(periods=period_ids):
            groups = Group.search([
                ('meta', 'in', [m.id for m in childs]),
                ])
        for group in groups:
            meta_sum[group.meta.id] += (getattr(group,name))

        for meta in metas:
            childs = cls.search([
                    ('parent', 'child_of', [meta.id]),
                    ])
            for child in childs:
                res[meta.id] += meta_sum[child.id]
            exp = Decimal(str(10.0 ** -meta.currency_digits))
            res[meta.id] = res[meta.id].quantize(exp)
        return res

    @staticmethod
    def order_baptism(tables):
        pool = Pool()
        Group = pool.get('tmi.meta.group')
        group = Group.__table__()
        table, _ = tables[None]

        return [CharLength(table.name), table.name]

    def get_baptism_target(self, name=None):
        pool = Pool()
        Configuration = pool.get('tmi.configuration')
        config = Configuration(1)
        target = config.get_multivalue('tmi_baptism_target')
        context = Transaction().context 
        start_date = context.get('start_date')
        end_date = context.get('end_date')
        value = self.child_value 
        if start_date and end_date: 
            months = diff_month(end_date, start_date)
            value = value * months
        total = 0
        if target and value:
            total = target * value 
        return total 

    def get_target(self, name):
        pool = Pool()
        Configuration = pool.get('tmi.configuration')
        if name not in {'tmi_baptism_target','tmi_tithe_target','tmi_offering_target','tmi_church_planting_target',
                'tmi_gathering_target','tmi_small_group_target','tmi_organizing_church_target',
                'tmi_praise_thanksgiving_target'}: 
            raise ValueError('Unknown name: %s' % name)
        config = Configuration(1)
        field = str(name)
        target = config.get_multivalue(field)
        context = Transaction().context 
        start_date = context.get('start_date')
        end_date = context.get('end_date')
        value = self.child_value 
        if start_date and end_date: 
            months = diff_month(end_date, start_date)
            value = value * months
        total = 0
        if target and value:
            total = target * value 
        return total

    def get_difference(self, name):
        pool = Pool()
        
        if name not in {'tmi_baptism_difference','tmi_tithe_difference','tmi_offering_difference','tmi_church_planting_difference',
                'tmi_gathering_difference','tmi_small_group_difference','tmi_organizing_church_difference',
                'tmi_praise_thanksgiving_difference'}: 
            raise ValueError('Unknown name: %s' % name)
        
        field = str(name)
        target_field = field.replace('difference','target')
        base_field = field.replace('tmi_','')
        base_field = base_field.replace('_difference','')

        
        target_field_value = getattr(self, target_field,None)
        base_field_value = getattr(self, base_field,None)

        difference = target_field_value - base_field_value
        
        return difference

    def get_percentage(self, name):
        pool = Pool()
        
        if name not in {'tmi_baptism_percentage','tmi_tithe_percentage','tmi_offering_percentage','tmi_church_planting_percentage',
                'tmi_gathering_percentage','tmi_small_group_percentage','tmi_organizing_church_percentage',
                'tmi_praise_thanksgiving_percentage'}: 
            raise ValueError('Unknown name: %s' % name)
        
        field = str(name)
        target_field = field.replace('percentage','target')
        base_field = field.replace('tmi_','')
        base_field = base_field.replace('_percentage','')

        target_field_value = getattr(self, target_field,None)
        base_field_value = getattr(self, base_field,None)

        difference = 0 
        if target_field_value and target_field_value != 0: 
            difference = base_field_value / target_field_value
            difference = round(difference, 2)

        return difference
Ejemplo n.º 23
0
class Work(sequence_ordered(), tree(separator='\\'), ModelSQL, ModelView):
    'Work Effort'
    __name__ = 'project.work'
    name = fields.Char('Name', required=True, select=True)
    type = fields.Selection([
            ('project', 'Project'),
            ('task', 'Task')
            ],
        'Type', required=True, select=True)
    company = fields.Many2One('company.company', 'Company', required=True,
        select=True)
    party = fields.Many2One('party.party', 'Party',
        states={
            'invisible': Eval('type') != 'project',
            },
        context={
            'company': Eval('company', -1),
            },
        depends={'company'})
    party_address = fields.Many2One('party.address', 'Contact Address',
        domain=[('party', '=', Eval('party'))],
        states={
            'invisible': Eval('type') != 'project',
            })
    timesheet_works = fields.One2Many(
        'timesheet.work', 'origin', 'Timesheet Works', readonly=True, size=1)
    timesheet_available = fields.Function(
        fields.Boolean('Available on timesheets'),
        'get_timesheet_available', setter='set_timesheet_available')
    timesheet_start_date = fields.Function(fields.Date('Timesheet Start',
            states={
                'invisible': ~Eval('timesheet_available'),
                }),
        'get_timesheet_date', setter='set_timesheet_date')
    timesheet_end_date = fields.Function(fields.Date('Timesheet End',
            states={
                'invisible': ~Eval('timesheet_available'),
                }),
        'get_timesheet_date', setter='set_timesheet_date')
    timesheet_duration = fields.Function(fields.TimeDelta('Duration',
            'company_work_time',
            help="Total time spent on this work and the sub-works."),
        'get_total')
    effort_duration = fields.TimeDelta('Effort', 'company_work_time',
        help="Estimated Effort for this work.")
    total_effort = fields.Function(fields.TimeDelta('Total Effort',
            'company_work_time',
            help="Estimated total effort for this work and the sub-works."),
        'get_total')
    progress = fields.Float('Progress',
        domain=['OR',
            ('progress', '=', None),
            [
                ('progress', '>=', 0),
                ('progress', '<=', 1),
                ],
            ],
        help='Estimated progress for this work.')
    total_progress = fields.Function(fields.Float('Total Progress',
            digits=(16, 4),
            help='Estimated total progress for this work and the sub-works.',
            states={
                'invisible': (
                    Eval('total_progress', None) == None),  # noqa: E711
                }),
        'get_total')
    comment = fields.Text('Comment')
    parent = fields.Many2One(
        'project.work', 'Parent', path='path', ondelete='RESTRICT',
        domain=[
            ('company', '=', Eval('company', -1)),
            ])
    path = fields.Char("Path", select=True)
    children = fields.One2Many('project.work', 'parent', 'Children',
        domain=[
            ('company', '=', Eval('company', -1)),
            ])
    status = fields.Many2One(
        'project.work.status', "Status", required=True, select=True,
        domain=[If(Bool(Eval('type')), ('types', 'in', Eval('type')), ())])

    @staticmethod
    def default_type():
        return 'task'

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

    @classmethod
    def default_status(cls):
        pool = Pool()
        WorkStatus = pool.get('project.work.status')
        return WorkStatus.get_default_status(cls.default_type())

    @classmethod
    def __register__(cls, module_name):
        TimesheetWork = Pool().get('timesheet.work')
        transaction = Transaction()
        cursor = transaction.connection.cursor()
        update = transaction.connection.cursor()
        table_project_work = cls.__table_handler__(module_name)
        project = cls.__table__()
        timesheet = TimesheetWork.__table__()

        work_exist = table_project_work.column_exist('work')
        add_parent = (not table_project_work.column_exist('parent')
            and work_exist)
        add_company = (not table_project_work.column_exist('company')
            and work_exist)
        add_name = (not table_project_work.column_exist('name')
            and work_exist)

        super(Work, cls).__register__(module_name)

        # Migration from 3.4: change effort into timedelta effort_duration
        if table_project_work.column_exist('effort'):
            cursor.execute(*project.select(project.id, project.effort,
                    where=project.effort != Null))
            for id_, effort in cursor:
                duration = datetime.timedelta(hours=effort)
                update.execute(*project.update(
                        [project.effort_duration],
                        [duration],
                        where=project.id == id_))
            table_project_work.drop_column('effort')

        # Migration from 3.6: add parent, company, drop required on work,
        # fill name
        if add_parent:
            second_project = cls.__table__()
            query = project.join(timesheet,
                condition=project.work == timesheet.id
                ).join(second_project,
                    condition=timesheet.parent == second_project.work
                    ).select(project.id, second_project.id)
            cursor.execute(*query)
            for id_, parent in cursor:
                update.execute(*project.update(
                        [project.parent],
                        [parent],
                        where=project.id == id_))
            cls._rebuild_tree('parent', None, 0)
        if add_company:
            cursor.execute(*project.join(timesheet,
                    condition=project.work == timesheet.id
                    ).select(project.id, timesheet.company))
            for id_, company in cursor:
                update.execute(*project.update(
                        [project.company],
                        [company],
                        where=project.id == id_))
        table_project_work.not_null_action('work', action='remove')
        if add_name:
            cursor.execute(*project.join(timesheet,
                    condition=project.work == timesheet.id
                    ).select(project.id, timesheet.name))
            for id_, name in cursor:
                update.execute(*project.update(
                        [project.name],
                        [name],
                        where=project.id == id_))

        # Migration from 4.0: remove work
        if work_exist:
            table_project_work.drop_constraint('work_uniq')
            cursor.execute(*project.select(project.id, project.work,
                    where=project.work != Null))
            for project_id, work_id in cursor:
                update.execute(*timesheet.update(
                        [timesheet.origin, timesheet.name],
                        ['%s,%s' % (cls.__name__, project_id), Null],
                        where=timesheet.id == work_id))
            table_project_work.drop_column('work')

        # Migration from 5.4: replace state by status
        table_project_work.not_null_action('state', action='remove')

        # Migration from 6.0: remove left and right
        table_project_work.drop_column('left')
        table_project_work.drop_column('right')

    @fields.depends('type', 'status')
    def on_change_type(self):
        pool = Pool()
        WorkState = pool.get('project.work.status')
        if (self.type
                and (not self.status
                    or self.type not in self.status.types)):
            self.status = WorkState.get_default_status(self.type)

    @fields.depends('status', 'progress')
    def on_change_status(self):
        if (self.status
                and self.status.progress is not None
                and self.status.progress > (self.progress or -1.0)):
            self.progress = self.status.progress

    @classmethod
    def index_set_field(cls, name):
        index = super(Work, cls).index_set_field(name)
        if name in {'timesheet_start_date', 'timesheet_end_date'}:
            index = cls.index_set_field('timesheet_available') + 1
        return index

    @classmethod
    def validate(cls, works):
        super(Work, cls).validate(works)
        for work in works:
            work.check_work_progress()

    def check_work_progress(self):
        pool = Pool()
        progress = -1 if self.progress is None else self.progress
        if (self.status.progress is not None
                and progress < self.status.progress):
            Lang = pool.get('ir.lang')
            lang = Lang.get()
            raise WorkProgressValidationError(
                gettext('project.msg_work_invalid_progress_status',
                    work=self.rec_name,
                    progress=lang.format('%.2f%%', self.status.progress * 100),
                    status=self.status.rec_name))
        if (self.status.progress == 1.0
                and not all(c.progress == 1.0 for c in self.children)):
            raise WorkProgressValidationError(
                gettext('project.msg_work_children_progress',
                    work=self.rec_name,
                    status=self.status.rec_name))
        if (self.parent
                and self.parent.progress == 1.0
                and not self.progress == 1.0):
            raise WorkProgressValidationError(
                gettext('project.msg_work_parent_progress',
                    work=self.rec_name,
                    parent=self.parent.rec_name))

    @property
    def effort_hours(self):
        if not self.effort_duration:
            return 0
        return self.effort_duration.total_seconds() / 60 / 60

    @property
    def total_effort_hours(self):
        if not self.total_effort:
            return 0
        return self.total_effort.total_seconds() / 60 / 60

    @property
    def timesheet_duration_hours(self):
        if not self.timesheet_duration:
            return 0
        return self.timesheet_duration.total_seconds() / 60 / 60

    @classmethod
    def default_timesheet_available(cls):
        return False

    def get_timesheet_available(self, name):
        return bool(self.timesheet_works)

    @classmethod
    def set_timesheet_available(cls, projects, name, value):
        pool = Pool()
        Timesheet = pool.get('timesheet.work')

        to_create = []
        to_delete = []
        for project in projects:
            if not project.timesheet_works and value:
                to_create.append({
                        'origin': str(project),
                        'company': project.company.id,
                        })
            elif project.timesheet_works and not value:
                to_delete.extend(project.timesheet_works)

        if to_create:
            Timesheet.create(to_create)
        if to_delete:
            Timesheet.delete(to_delete)

    def get_timesheet_date(self, name):
        if self.timesheet_works:
            func = {
                'timesheet_start_date': min,
                'timesheet_end_date': max,
                }[name]
            return func(getattr(w, name) for w in self.timesheet_works)

    @classmethod
    def set_timesheet_date(cls, projects, name, value):
        pool = Pool()
        Timesheet = pool.get('timesheet.work')
        timesheets = [w for p in projects for w in p.timesheet_works]
        if timesheets:
            Timesheet.write(timesheets, {
                    name: value,
                    })

    @classmethod
    def sum_tree(cls, works, values, parents):
        result = values.copy()
        works = set((w.id for w in works))
        leafs = works - set(parents.values())
        while leafs:
            for work in leafs:
                works.remove(work)
                parent = parents.get(work)
                if parent in result:
                    result[parent] += result[work]
            next_leafs = set(works)
            for work in works:
                parent = parents.get(work)
                if not parent:
                    continue
                if parent in next_leafs and parent in works:
                    next_leafs.remove(parent)
            leafs = next_leafs
        return result

    @classmethod
    def get_total(cls, works, names):
        cursor = Transaction().connection.cursor()
        table = cls.__table__()

        works = cls.search([
                ('parent', 'child_of', [w.id for w in works]),
                ])
        work_ids = [w.id for w in works]
        parents = {}
        for sub_ids in grouped_slice(work_ids):
            where = reduce_ids(table.id, sub_ids)
            cursor.execute(*table.select(table.id, table.parent,
                    where=where))
            parents.update(cursor)

        if 'total_progress' in names and 'total_effort' not in names:
            names = list(names)
            names.append('total_effort')

        result = {}
        for name in names:
            values = getattr(cls, '_get_%s' % name)(works)
            result[name] = cls.sum_tree(works, values, parents)

        if 'total_progress' in names:
            digits = cls.total_progress.digits[1]
            total_progress = result['total_progress']
            total_effort = result['total_effort']
            for work in works:
                if total_effort[work.id]:
                    total_progress[work.id] = round(total_progress[work.id]
                        / (total_effort[work.id].total_seconds() / 60 / 60),
                        digits)
                else:
                    total_effort[work.id] = None
        return result

    @classmethod
    def _get_total_effort(cls, works):
        return {w.id: w.effort_duration or datetime.timedelta() for w in works}

    @classmethod
    def _get_timesheet_duration(cls, works):
        durations = {}
        for work in works:
            value = datetime.timedelta()
            for timesheet_work in work.timesheet_works:
                if timesheet_work.duration:
                    value += timesheet_work.duration
            durations[work.id] = value
        return durations

    @classmethod
    def _get_total_progress(cls, works):
        return {w.id: w.effort_hours * (w.progress or 0) for w in works}

    @classmethod
    def copy(cls, project_works, default=None):
        pool = Pool()
        WorkStatus = pool.get('project.work.status')
        if default is None:
            default = {}
        else:
            default = default.copy()
        default.setdefault('children', None)
        default.setdefault('progress', None)
        default.setdefault(
            'status', lambda data: WorkStatus.get_default_status(data['type']))
        return super().copy(project_works, default=default)

    @classmethod
    def delete(cls, project_works):
        TimesheetWork = Pool().get('timesheet.work')

        # Get the timesheet works linked to the project works
        timesheet_works = [
            w for pw in project_works for w in pw.timesheet_works]

        super(Work, cls).delete(project_works)

        if timesheet_works:
            with Transaction().set_context(_check_access=False):
                TimesheetWork.delete(timesheet_works)

    @classmethod
    def search_global(cls, text):
        for record, rec_name, icon in super(Work, cls).search_global(text):
            icon = icon or 'tryton-project'
            yield record, rec_name, icon
Ejemplo n.º 24
0
class Category(DeactivableMixin, tree(separator=' / '), ModelSQL, ModelView):
    "Category"
    __name__ = 'party.category'
    name = fields.Char('Name',
                       required=True,
                       states=STATES,
                       translate=True,
                       depends=DEPENDS,
                       help="The main identifier of the category.")
    parent = fields.Many2One('party.category',
                             'Parent',
                             select=True,
                             states=STATES,
                             depends=DEPENDS,
                             help="Add the category below the parent.")
    childs = fields.One2Many('party.category',
                             'parent',
                             'Children',
                             states=STATES,
                             depends=DEPENDS,
                             help="Add children below the category.")

    @classmethod
    def __setup__(cls):
        super(Category, cls).__setup__()
        t = cls.__table__()
        cls._sql_constraints = [
            ('name_parent_exclude',
             Exclude(t, (t.name, Equal), (Coalesce(t.parent, -1), Equal)),
             'party.msg_category_name_unique'),
        ]
        cls._order.insert(0, ('name', 'ASC'))

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

        table_h = cls.__table_handler__(module_name)

        # Migration from 4.6: replace unique by exclude
        table_h.drop_constraint('name_parent_uniq')

    @classmethod
    def validate(cls, categories):
        super(Category, cls).validate(categories)
        cls.check_recursion(categories)
        for category in categories:
            category.check_name()

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

    def get_rec_name(self, name):
        if self.parent:
            return self.parent.get_rec_name(name) + SEPARATOR + self.name
        return self.name

    @classmethod
    def search_rec_name(cls, name, clause):
        if isinstance(clause[2], str):
            values = clause[2].split(SEPARATOR)
            values.reverse()
            domain = []
            field = 'name'
            for name in values:
                domain.append((field, clause[1], name))
                field = 'parent.' + field
            return domain
        # TODO Handle list
        return [('name', ) + tuple(clause[1:])]