コード例 #1
0
ファイル: stock_picking.py プロジェクト: parthivgls/flectra-1
class Picking(models.Model):
    _name = "stock.picking"
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _description = "Transfer"
    _order = "priority desc, date asc, id desc"

    name = fields.Char(
        'Reference', default='/',
        copy=False,  index=True,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    origin = fields.Char(
        'Source Document', index=True,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
        help="Reference of the document")
    note = fields.Text('Notes')

    backorder_id = fields.Many2one(
        'stock.picking', 'Back Order of',
        copy=False, index=True,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
        help="If this shipment was split, then this field links to the shipment which contains the already processed part.")

    move_type = fields.Selection([
        ('direct', 'As soon as possible'), ('one', 'When all products are ready')], 'Shipping Policy',
        default='direct', required=True,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
        help="It specifies goods to be deliver partially or all at once")

    state = fields.Selection([
        ('draft', 'Draft'),
        ('waiting', 'Waiting Another Operation'),
        ('confirmed', 'Waiting'),
        ('assigned', 'Ready'),
        ('done', 'Done'),
        ('cancel', 'Cancelled'),
    ], string='Status', compute='_compute_state',
        copy=False, index=True, readonly=True, store=True, track_visibility='onchange',
        help=" * Draft: not confirmed yet and will not be scheduled until confirmed.\n"
             " * Waiting Another Operation: waiting for another move to proceed before it becomes automatically available (e.g. in Make-To-Order flows).\n"
             " * Waiting: if it is not ready to be sent because the required products could not be reserved.\n"
             " * Ready: products are reserved and ready to be sent. If the shipping policy is 'As soon as possible' this happens as soon as anything is reserved.\n"
             " * Done: has been processed, can't be modified or cancelled anymore.\n"
             " * Cancelled: has been cancelled, can't be confirmed anymore.")

    group_id = fields.Many2one(
        'procurement.group', 'Procurement Group',
        readonly=True, related='move_lines.group_id', store=True)

    priority = fields.Selection(
        PROCUREMENT_PRIORITIES, string='Priority',
        compute='_compute_priority', inverse='_set_priority', store=True,
        # default='1', required=True,  # TDE: required, depending on moves ? strange
        index=True, track_visibility='onchange',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
        help="Priority for this picking. Setting manually a value here would set it as priority for all the moves")
    scheduled_date = fields.Datetime(
        'Scheduled Date', compute='_compute_scheduled_date', inverse='_set_scheduled_date', store=True,
        index=True, track_visibility='onchange',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
        help="Scheduled time for the first part of the shipment to be processed. Setting manually a value here would set it as expected date for all the stock moves.")
    date = fields.Datetime(
        'Creation Date',
        default=fields.Datetime.now, index=True, track_visibility='onchange',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
        help="Creation Date, usually the time of the order")
    date_done = fields.Datetime('Date of Transfer', copy=False, readonly=True, help="Completion Date of Transfer")

    location_id = fields.Many2one(
        'stock.location', "Source Location",
        default=lambda self: self.env['stock.picking.type'].browse(self._context.get('default_picking_type_id')).default_location_src_id,
        readonly=True, required=True,
        states={'draft': [('readonly', False)]})
    location_dest_id = fields.Many2one(
        'stock.location', "Destination Location",
        default=lambda self: self.env['stock.picking.type'].browse(self._context.get('default_picking_type_id')).default_location_dest_id,
        readonly=True, required=True,
        states={'draft': [('readonly', False)]})
    move_lines = fields.One2many('stock.move', 'picking_id', string="Stock Moves", copy=True)
    has_scrap_move = fields.Boolean(
        'Has Scrap Moves', compute='_has_scrap_move')
    picking_type_id = fields.Many2one(
        'stock.picking.type', 'Operation Type',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]})
    picking_type_code = fields.Selection([
        ('incoming', 'Vendors'),
        ('outgoing', 'Customers'),
        ('internal', 'Internal')], related='picking_type_id.code',
        readonly=True)
    picking_type_entire_packs = fields.Boolean(related='picking_type_id.show_entire_packs',
        readonly=True)

    partner_id = fields.Many2one(
        'res.partner', 'Partner',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})
    company_id = fields.Many2one(
        'res.company', 'Company',
        default=lambda self: self.env['res.company']._company_default_get('stock.picking'),
        index=True, required=True,
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]})

    branch_id = fields.Many2one('res.branch', 'Branch', ondelete="restrict",
                                default=lambda self: self.env['res.users']._get_default_branch(),
                                states={'done': [('readonly', True)],
                                        'cancel': [('readonly', True)]})

    move_line_ids = fields.One2many('stock.move.line', 'picking_id', 'Operations')

    move_line_exist = fields.Boolean(
        'Has Pack Operations', compute='_compute_move_line_exist',
        help='Check the existence of pack operation on the picking')

    has_packages = fields.Boolean(
        'Has Packages', compute='_compute_has_packages',
        help='Check the existence of destination packages on move lines')

    entire_package_ids = fields.One2many('stock.quant.package', compute='_compute_entire_package_ids',
                                         help='Those are the entire packages of a picking shown in the view of operations')
    entire_package_detail_ids = fields.One2many('stock.quant.package', compute='_compute_entire_package_ids',
                                                help='Those are the entire packages of a picking shown in the view of detailed operations')

    show_check_availability = fields.Boolean(
        compute='_compute_show_check_availability',
        help='Technical field used to compute whether the check availability button should be shown.')
    show_mark_as_todo = fields.Boolean(
        compute='_compute_show_mark_as_todo',
        help='Technical field used to compute whether the mark as todo button should be shown.')
    show_validate = fields.Boolean(
        compute='_compute_show_validate',
        help='Technical field used to compute whether the validate should be shown.')

    owner_id = fields.Many2one(
        'res.partner', 'Owner',
        states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
        help="Default Owner")
    printed = fields.Boolean('Printed')
    is_locked = fields.Boolean(default=True, help='When the picking is not done this allows changing the '
                               'initial demand. When the picking is done this allows '
                               'changing the done quantities.')
    # Used to search on pickings
    product_id = fields.Many2one('product.product', 'Product', related='move_lines.product_id', readonly=True)
    show_operations = fields.Boolean(compute='_compute_show_operations')
    show_lots_text = fields.Boolean(compute='_compute_show_lots_text')
    has_tracking = fields.Boolean(compute='_compute_has_tracking')

    _sql_constraints = [
        ('name_uniq', 'unique(name, company_id)', 'Reference must be unique per company!'),
    ]

    def _compute_has_tracking(self):
        for picking in self:
            picking.has_tracking = any(m.has_tracking != 'none' for m in picking.move_lines)

    @api.depends('picking_type_id.show_operations')
    def _compute_show_operations(self):
        for picking in self:
            if self.env.context.get('force_detailed_view'):
                picking.show_operations = True
                continue
            if picking.picking_type_id.show_operations:
                if (picking.state == 'draft' and not self.env.context.get('planned_picking')) or picking.state != 'draft':
                    picking.show_operations = True
                else:
                    picking.show_operations = False
            else:
                picking.show_operations = False

    @api.depends('move_line_ids', 'picking_type_id.use_create_lots', 'picking_type_id.use_existing_lots', 'state')
    def _compute_show_lots_text(self):
        group_production_lot_enabled = self.user_has_groups('stock.group_production_lot')
        for picking in self:
            if not picking.move_line_ids:
                picking.show_lots_text = False
            elif group_production_lot_enabled and picking.picking_type_id.use_create_lots \
                    and not picking.picking_type_id.use_existing_lots and picking.state != 'done':
                picking.show_lots_text = True
            else:
                picking.show_lots_text = False

    @api.depends('move_type', 'move_lines.state', 'move_lines.picking_id')
    @api.one
    def _compute_state(self):
        ''' State of a picking depends on the state of its related stock.move
        - Draft: only used for "planned pickings"
        - Waiting: if the picking is not ready to be sent so if
          - (a) no quantity could be reserved at all or if
          - (b) some quantities could be reserved and the shipping policy is "deliver all at once"
        - Waiting another move: if the picking is waiting for another move
        - Ready: if the picking is ready to be sent so if:
          - (a) all quantities are reserved or if
          - (b) some quantities could be reserved and the shipping policy is "as soon as possible"
        - Done: if the picking is done.
        - Cancelled: if the picking is cancelled
        '''
        if not self.move_lines:
            self.state = 'draft'
        elif any(move.state == 'draft' for move in self.move_lines):  # TDE FIXME: should be all ?
            self.state = 'draft'
        elif all(move.state == 'cancel' for move in self.move_lines):
            self.state = 'cancel'
        elif all(move.state in ['cancel', 'done'] for move in self.move_lines):
            self.state = 'done'
        else:
            relevant_move_state = self.move_lines._get_relevant_state_among_moves()
            if relevant_move_state == 'partially_available':
                self.state = 'assigned'
            else:
                self.state = relevant_move_state

    @api.one
    @api.depends('move_lines.priority')
    def _compute_priority(self):
        if self.mapped('move_lines'):
            priorities = [priority for priority in self.mapped('move_lines.priority') if priority] or ['1']
            self.priority = max(priorities)
        else:
            self.priority = '1'

    @api.one
    def _set_priority(self):
        self.move_lines.write({'priority': self.priority})

    @api.one
    @api.depends('move_lines.date_expected')
    def _compute_scheduled_date(self):
        if self.move_type == 'direct':
            self.scheduled_date = min(self.move_lines.mapped('date_expected') or [fields.Datetime.now()])
        else:
            self.scheduled_date = max(self.move_lines.mapped('date_expected') or [fields.Datetime.now()])

    @api.one
    def _set_scheduled_date(self):
        self.move_lines.write({'date_expected': self.scheduled_date})

    @api.one
    def _has_scrap_move(self):
        # TDE FIXME: better implementation
        self.has_scrap_move = bool(self.env['stock.move'].search_count([('picking_id', '=', self.id), ('scrapped', '=', True)]))

    @api.one
    def _compute_move_line_exist(self):
        self.move_line_exist = bool(self.move_line_ids)

    @api.one
    def _compute_has_packages(self):
        self.has_packages = self.move_line_ids.filtered(lambda ml: ml.result_package_id)

    def _compute_entire_package_ids(self):
        """ This compute method populate the two one2Many containing all entire packages of the picking.
            An entire package is a package that is entirely reserved to be moved from a location to another one.
        """
        for picking in self:
            packages = self.env['stock.quant.package']
            packages_to_check = picking.move_line_ids\
                .filtered(lambda ml: ml.result_package_id and ml.package_id.id == ml.result_package_id.id)\
                .mapped('package_id')
            for package_to_check in packages_to_check:
                if picking.state in ('done', 'cancel') or picking._check_move_lines_map_quant_package(package_to_check):
                    packages |= package_to_check
            picking.entire_package_ids = packages
            picking.entire_package_detail_ids = packages

    @api.multi
    def _compute_show_check_availability(self):
        for picking in self:
            has_moves_to_reserve = any(
                move.state in ('waiting', 'confirmed', 'partially_available') and
                float_compare(move.product_uom_qty, 0, precision_rounding=move.product_uom.rounding)
                for move in picking.move_lines
            )
            picking.show_check_availability = picking.is_locked and picking.state in ('confirmed', 'waiting', 'assigned') and has_moves_to_reserve

    @api.multi
    @api.depends('state', 'move_lines')
    def _compute_show_mark_as_todo(self):
        for picking in self:
            if not picking.move_lines:
                picking.show_mark_as_todo = False
            elif self._context.get('planned_picking') and picking.state == 'draft':
                picking.show_mark_as_todo = True
            elif picking.state != 'draft' or not picking.id:
                picking.show_mark_as_todo = False
            else:
                picking.show_mark_as_todo = True

    @api.multi
    @api.depends('state', 'is_locked')
    def _compute_show_validate(self):
        for picking in self:
            if self._context.get('planned_picking') and picking.state == 'draft':
                picking.show_validate = False
            elif picking.state not in ('draft', 'waiting', 'confirmed', 'assigned') or not picking.is_locked:
                picking.show_validate = False
            else:
                picking.show_validate = True

    @api.onchange('picking_type_id', 'partner_id')
    def onchange_picking_type(self):
        if self.picking_type_id:
            if self.picking_type_id.default_location_src_id:
                location_id = self.picking_type_id.default_location_src_id.id
            elif self.partner_id:
                location_id = self.partner_id.property_stock_supplier.id
            else:
                customerloc, location_id = self.env['stock.warehouse']._get_partner_locations()

            if self.picking_type_id.default_location_dest_id:
                location_dest_id = self.picking_type_id.default_location_dest_id.id
            elif self.partner_id:
                location_dest_id = self.partner_id.property_stock_customer.id
            else:
                location_dest_id, supplierloc = self.env['stock.warehouse']._get_partner_locations()

            if self.state == 'draft':
                self.location_id = location_id
                self.location_dest_id = location_dest_id
        # TDE CLEANME move into onchange_partner_id
        if self.partner_id:
            if self.partner_id.picking_warn == 'no-message' and self.partner_id.parent_id:
                partner = self.partner_id.parent_id
            elif self.partner_id.picking_warn not in ('no-message', 'block') and self.partner_id.parent_id.picking_warn == 'block':
                partner = self.partner_id.parent_id
            else:
                partner = self.partner_id
            if partner.picking_warn != 'no-message':
                if partner.picking_warn == 'block':
                    self.partner_id = False
                return {'warning': {
                    'title': ("Warning for %s") % partner.name,
                    'message': partner.picking_warn_msg
                }}

    @api.model
    def create(self, vals):
        # TDE FIXME: clean that brol
        defaults = self.default_get(['name', 'picking_type_id'])
        if vals.get('name', '/') == '/' and defaults.get('name', '/') == '/' and vals.get('picking_type_id', defaults.get('picking_type_id')):
            vals['name'] = self.env['stock.picking.type'].browse(vals.get('picking_type_id', defaults.get('picking_type_id'))).sequence_id.next_by_id()

        # TDE FIXME: what ?
        # As the on_change in one2many list is WIP, we will overwrite the locations on the stock moves here
        # As it is a create the format will be a list of (0, 0, dict)
        if vals.get('move_lines') and vals.get('location_id') and vals.get('location_dest_id'):
            for move in vals['move_lines']:
                if len(move) == 3 and move[0] == 0:
                    move[2]['location_id'] = vals['location_id']
                    move[2]['location_dest_id'] = vals['location_dest_id']
        res = super(Picking, self).create(vals)
        res._autoconfirm_picking()
        return res

    @api.multi
    def write(self, vals):
        res = super(Picking, self).write(vals)
        # Change locations of moves if those of the picking change
        after_vals = {}
        if vals.get('location_id'):
            after_vals['location_id'] = vals['location_id']
        if vals.get('location_dest_id'):
            after_vals['location_dest_id'] = vals['location_dest_id']
        if after_vals:
            self.mapped('move_lines').filtered(lambda move: not move.scrapped).write(after_vals)
        if vals.get('move_lines'):
            # Do not run autoconfirm if any of the moves has an initial demand. If an initial demand
            # is present in any of the moves, it means the picking was created through the "planned
            # transfer" mechanism.
            pickings_to_not_autoconfirm = self.env['stock.picking']
            for picking in self:
                if picking.state != 'draft':
                    continue
                for move in picking.move_lines:
                    if not float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding):
                        pickings_to_not_autoconfirm |= picking
                        break
            (self - pickings_to_not_autoconfirm)._autoconfirm_picking()
        return res

    @api.multi
    def unlink(self):
        self.mapped('move_lines')._action_cancel()
        self.with_context(prefetch_fields=False).mapped('move_lines').unlink()  # Checks if moves are not done
        return super(Picking, self).unlink()

    # Actions
    # ----------------------------------------

    @api.one
    def action_assign_owner(self):
        self.move_line_ids.write({'owner_id': self.owner_id.id})

    def action_assign_partner(self):
        for picking in self:
            picking.move_lines.write({'partner_id': picking.partner_id.id})

    @api.multi
    def do_print_picking(self):
        self.write({'printed': True})
        return self.env.ref('stock.action_report_picking').report_action(self)

    @api.multi
    def action_confirm(self):
        # call `_action_confirm` on every draft move
        self.mapped('move_lines')\
            .filtered(lambda move: move.state == 'draft')\
            ._action_confirm()
        # call `_action_assign` on every confirmed move which location_id bypasses the reservation
        self.filtered(lambda picking: picking.location_id.usage in ('supplier', 'inventory', 'production') and picking.state == 'confirmed')\
            .mapped('move_lines')._action_assign()
        if self.env.context.get('planned_picking') and len(self) == 1:
            action = self.env.ref('stock.action_picking_form')
            result = action.read()[0]
            result['res_id'] = self.id
            result['context'] = {
                'search_default_picking_type_id': [self.picking_type_id.id],
                'default_picking_type_id': self.picking_type_id.id,
                'contact_display': 'partner_address',
                'planned_picking': False,
            }
            return result
        else:
            return True

    @api.multi
    def action_assign(self):
        """ Check availability of picking moves.
        This has the effect of changing the state and reserve quants on available moves, and may
        also impact the state of the picking as it is computed based on move's states.
        @return: True
        """
        self.filtered(lambda picking: picking.state == 'draft').action_confirm()
        moves = self.mapped('move_lines').filtered(lambda move: move.state not in ('draft', 'cancel', 'done'))
        if not moves:
            raise UserError(_('Nothing to check the availability for.'))
        moves._action_assign()
        return True

    @api.multi
    def force_assign(self):
        """ Changes state of picking to available if moves are confirmed or waiting.
        @return: True
        """
        self.mapped('move_lines').filtered(lambda move: move.state in ['confirmed', 'waiting', 'partially_available'])._force_assign()
        return True

    @api.multi
    def action_cancel(self):
        self.mapped('move_lines')._action_cancel()
        self.write({'is_locked': True})
        return True

    @api.multi
    def action_done(self):
        """Changes picking state to done by processing the Stock Moves of the Picking

        Normally that happens when the button "Done" is pressed on a Picking view.
        @return: True
        """
        # TDE FIXME: remove decorator when migration the remaining
        todo_moves = self.mapped('move_lines').filtered(lambda self: self.state in ['draft', 'waiting', 'partially_available', 'assigned', 'confirmed'])
        # Check if there are ops not linked to moves yet
        for pick in self:
            # # Explode manually added packages
            # for ops in pick.move_line_ids.filtered(lambda x: not x.move_id and not x.product_id):
            #     for quant in ops.package_id.quant_ids: #Or use get_content for multiple levels
            #         self.move_line_ids.create({'product_id': quant.product_id.id,
            #                                    'package_id': quant.package_id.id,
            #                                    'result_package_id': ops.result_package_id,
            #                                    'lot_id': quant.lot_id.id,
            #                                    'owner_id': quant.owner_id.id,
            #                                    'product_uom_id': quant.product_id.uom_id.id,
            #                                    'product_qty': quant.qty,
            #                                    'qty_done': quant.qty,
            #                                    'location_id': quant.location_id.id, # Could be ops too
            #                                    'location_dest_id': ops.location_dest_id.id,
            #                                    'picking_id': pick.id
            #                                    }) # Might change first element
            # # Link existing moves or add moves when no one is related
            for ops in pick.move_line_ids.filtered(lambda x: not x.move_id):
                # Search move with this product
                moves = pick.move_lines.filtered(lambda x: x.product_id == ops.product_id)
                moves = sorted(moves, key=lambda m: m.quantity_done < m.product_qty, reverse=True)
                if moves:
                    ops.move_id = moves[0].id
                else:
                    new_move = self.env['stock.move'].create({
                                                    'name': _('New Move:') + ops.product_id.display_name,
                                                    'product_id': ops.product_id.id,
                                                    'product_uom_qty': ops.qty_done,
                                                    'product_uom': ops.product_uom_id.id,
                                                    'location_id': pick.location_id.id,
                                                    'location_dest_id': pick.location_dest_id.id,
                                                    'picking_id': pick.id,
                                                   })
                    ops.move_id = new_move.id
                    new_move._action_confirm()
                    todo_moves |= new_move
                    #'qty_done': ops.qty_done})
        todo_moves._action_done()
        self.write({'date_done': fields.Datetime.now()})
        return True

    # Backward compatibility
    # Problem with fixed reference to a function:
    # it doesn't allow for overriding action_done() through do_transfer
    # get rid of me in master (and make me private ?)
    def do_transfer(self):
        return self.action_done()

    def _check_move_lines_map_quant_package(self, package):
        """ This method checks that all product of the package (quant) are well present in the move_line_ids of the picking. """
        all_in = True
        pack_move_lines = self.move_line_ids.filtered(lambda ml: ml.package_id == package)
        keys = ['product_id', 'lot_id']
        precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')

        grouped_quants = {}
        for k, g in groupby(sorted(package.quant_ids, key=itemgetter(*keys)), key=itemgetter(*keys)):
            grouped_quants[k] = sum(self.env['stock.quant'].concat(*list(g)).mapped('quantity'))

        grouped_ops = {}
        for k, g in groupby(sorted(pack_move_lines, key=itemgetter(*keys)), key=itemgetter(*keys)):
            grouped_ops[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('product_qty'))
        if any(not float_is_zero(grouped_quants.get(key, 0) - grouped_ops.get(key, 0), precision_digits=precision_digits) for key in grouped_quants) \
                or any(not float_is_zero(grouped_ops.get(key, 0) - grouped_quants.get(key, 0), precision_digits=precision_digits) for key in grouped_ops):
            all_in = False
        return all_in

    @api.multi
    def _check_entire_pack(self):
        """ This function check if entire packs are moved in the picking"""
        for picking in self:
            origin_packages = picking.move_line_ids.mapped("package_id")
            for pack in origin_packages:
                if picking._check_move_lines_map_quant_package(pack):
                    picking.move_line_ids.filtered(lambda ml: ml.package_id == pack).write({'result_package_id': pack.id})

    @api.multi
    def do_unreserve(self):
        for picking in self:
            picking.move_lines._do_unreserve()

    @api.multi
    def button_validate(self):
        self.ensure_one()
        if not self.move_lines and not self.move_line_ids:
            raise UserError(_('Please add some lines to move'))

        # If no lots when needed, raise error
        picking_type = self.picking_type_id
        precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        no_quantities_done = all(float_is_zero(move_line.qty_done, precision_digits=precision_digits) for move_line in self.move_line_ids.filtered(lambda m: m.state not in ('done', 'cancel')))
        no_reserved_quantities = all(float_is_zero(move_line.product_qty, precision_rounding=move_line.product_uom_id.rounding) for move_line in self.move_line_ids)
        if no_reserved_quantities and no_quantities_done:
            raise UserError(_('You cannot validate a transfer if you have not processed any quantity. You should rather cancel the transfer.'))

        if picking_type.use_create_lots or picking_type.use_existing_lots:
            lines_to_check = self.move_line_ids
            if not no_quantities_done:
                lines_to_check = lines_to_check.filtered(
                    lambda line: float_compare(line.qty_done, 0,
                                               precision_rounding=line.product_uom_id.rounding)
                )

            for line in lines_to_check:
                product = line.product_id
                if product and product.tracking != 'none':
                    if not line.lot_name and not line.lot_id:
                        raise UserError(_('You need to supply a lot/serial number for %s.') % product.display_name)

        if no_quantities_done:
            view = self.env.ref('stock.view_immediate_transfer')
            wiz = self.env['stock.immediate.transfer'].create({'pick_ids': [(4, self.id)]})
            return {
                'name': _('Immediate Transfer?'),
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'stock.immediate.transfer',
                'views': [(view.id, 'form')],
                'view_id': view.id,
                'target': 'new',
                'res_id': wiz.id,
                'context': self.env.context,
            }

        if self._get_overprocessed_stock_moves() and not self._context.get('skip_overprocessed_check'):
            view = self.env.ref('stock.view_overprocessed_transfer')
            wiz = self.env['stock.overprocessed.transfer'].create({'picking_id': self.id})
            return {
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'stock.overprocessed.transfer',
                'views': [(view.id, 'form')],
                'view_id': view.id,
                'target': 'new',
                'res_id': wiz.id,
                'context': self.env.context,
            }

        # Check backorder should check for other barcodes
        if self._check_backorder():
            return self.action_generate_backorder_wizard()
        self.action_done()
        return

    def action_generate_backorder_wizard(self):
        view = self.env.ref('stock.view_backorder_confirmation')
        wiz = self.env['stock.backorder.confirmation'].create({'pick_ids': [(4, p.id) for p in self]})
        return {
            'name': _('Create Backorder?'),
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'stock.backorder.confirmation',
            'views': [(view.id, 'form')],
            'view_id': view.id,
            'target': 'new',
            'res_id': wiz.id,
            'context': self.env.context,
        }

    def action_toggle_is_locked(self):
        self.ensure_one()
        self.is_locked = not self.is_locked
        return True

    def _check_backorder(self):
        """ This method will loop over all the move lines of self and
        check if creating a backorder is necessary. This method is
        called during button_validate if the user has already processed
        some quantities and in the immediate transfer wizard that is
        displayed if the user has not processed any quantities.

        :return: True if a backorder is necessary else False
        """
        quantity_todo = {}
        quantity_done = {}
        for move in self.mapped('move_lines'):
            quantity_todo.setdefault(move.product_id.id, 0)
            quantity_done.setdefault(move.product_id.id, 0)
            quantity_todo[move.product_id.id] += move.product_uom_qty
            quantity_done[move.product_id.id] += move.quantity_done
        for ops in self.mapped('move_line_ids').filtered(lambda x: x.package_id and not x.product_id and not x.move_id):
            for quant in ops.package_id.quant_ids:
                quantity_done.setdefault(quant.product_id.id, 0)
                quantity_done[quant.product_id.id] += quant.qty
        for pack in self.mapped('move_line_ids').filtered(lambda x: x.product_id and not x.move_id):
            quantity_done.setdefault(pack.product_id.id, 0)
            quantity_done[pack.product_id.id] += pack.product_uom_id._compute_quantity(pack.qty_done, pack.product_id.uom_id)
        return any(quantity_done[x] < quantity_todo.get(x, 0) for x in quantity_done)

    @api.multi
    def _autoconfirm_picking(self):
        if not self._context.get('planned_picking'):
            for picking in self.filtered(lambda picking: picking.state not in ('done', 'cancel') and picking.move_lines):
                picking.action_confirm()

    def _get_overprocessed_stock_moves(self):
        self.ensure_one()
        return self.move_lines.filtered(
            lambda move: move.product_uom_qty != 0 and float_compare(move.quantity_done, move.product_uom_qty,
                                                                     precision_rounding=move.product_uom.rounding) == 1
        )

    @api.multi
    def _create_backorder(self, backorder_moves=[]):
        """ Move all non-done lines into a new backorder picking.
        """
        backorders = self.env['stock.picking']
        for picking in self:
            moves_to_backorder = picking.move_lines.filtered(lambda x: x.state not in ('done', 'cancel'))
            if moves_to_backorder:
                backorder_picking = picking.copy({
                    'name': '/',
                    'move_lines': [],
                    'move_line_ids': [],
                    'backorder_id': picking.id
                })
                picking.message_post(
                    _('The backorder <a href=# data-oe-model=stock.picking data-oe-id=%d>%s</a> has been created.') % (
                        backorder_picking.id, backorder_picking.name))
                moves_to_backorder.write({'picking_id': backorder_picking.id})
                moves_to_backorder.mapped('move_line_ids').write({'picking_id': backorder_picking.id})
                backorder_picking.action_assign()
                backorders |= backorder_picking
        return backorders

    def _put_in_pack(self):
        package = False
        for pick in self.filtered(lambda p: p.state not in ('done', 'cancel')):
            operations = pick.move_line_ids.filtered(lambda o: o.qty_done > 0 and not o.result_package_id)
            operation_ids = self.env['stock.move.line']
            if operations:
                package = self.env['stock.quant.package'].create({})
                for operation in operations:
                    if float_compare(operation.qty_done, operation.product_uom_qty, precision_rounding=operation.product_uom_id.rounding) >= 0:
                        operation_ids |= operation
                    else:
                        quantity_left_todo = float_round(
                            operation.product_uom_qty - operation.qty_done,
                            precision_rounding=operation.product_uom_id.rounding,
                            rounding_method='UP')
                        done_to_keep = operation.qty_done
                        new_operation = operation.copy(
                            default={'product_uom_qty': 0, 'qty_done': operation.qty_done})
                        operation.write({'product_uom_qty': quantity_left_todo, 'qty_done': 0.0})
                        new_operation.write({'product_uom_qty': done_to_keep})
                        operation_ids |= new_operation

                operation_ids.write({'result_package_id': package.id})
            else:
                raise UserError(_('Please process some quantities to put in the pack first!'))
        return package

    def put_in_pack(self):
        return self._put_in_pack()

    def button_scrap(self):
        self.ensure_one()
        products = self.env['product.product']
        for move in self.move_lines:
            if move.state not in ('draft', 'cancel') and move.product_id.type in ('product', 'consu'):
                products |= move.product_id
        return {
            'name': _('Scrap'),
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'stock.scrap',
            'view_id': self.env.ref('stock.stock_scrap_form_view2').id,
            'type': 'ir.actions.act_window',
            'context': {'default_picking_id': self.id, 'product_ids': products.ids},
            'target': 'new',
        }

    def action_see_move_scrap(self):
        self.ensure_one()
        action = self.env.ref('stock.action_stock_scrap').read()[0]
        scraps = self.env['stock.scrap'].search([('picking_id', '=', self.id)])
        action['domain'] = [('id', 'in', scraps.ids)]
        return action

    def action_see_packages(self):
        self.ensure_one()
        action = self.env.ref('stock.action_package_view').read()[0]
        packages = self.move_line_ids.mapped('result_package_id')
        action['domain'] = [('id', 'in', packages.ids)]
        action['context'] = {'picking_id': self.id}
        return action

    def action_picking_move_tree(self):
        action = self.env.ref('stock.stock_move_action').read()[0]
        action['views'] = [
            (self.env.ref('stock.view_picking_move_tree').id, 'tree'),
        ]
        action['context'] = self.env.context
        action['domain'] = [('picking_id', 'in', self.ids)]
        return action
コード例 #2
0
ファイル: res_users.py プロジェクト: yemanadep/Flectra
class Users(models.Model):
    """ User class. A res.users record models an OpenERP user and is different
        from an employee.

        res.users class now inherits from res.partner. The partner model is
        used to store the data related to the partner: lang, name, address,
        avatar, ... The user model is now dedicated to technical data.
    """
    _name = "res.users"
    _description = 'Users'
    _inherits = {'res.partner': 'partner_id'}
    _order = 'name, login'
    __uid_cache = defaultdict(dict)  # {dbname: {uid: password}}

    # User can write on a few of his own fields (but not his groups for example)
    SELF_WRITEABLE_FIELDS = [
        'signature', 'action_id', 'company_id', 'email', 'name', 'image',
        'image_medium', 'image_small', 'lang', 'tz'
    ]
    # User can read a few of his own fields
    SELF_READABLE_FIELDS = [
        'signature', 'company_id', 'login', 'email', 'name', 'image',
        'image_medium', 'image_small', 'lang', 'tz', 'tz_offset', 'groups_id',
        'partner_id', '__last_update', 'action_id'
    ]

    def _default_groups(self):
        default_user = self.env.ref('base.default_user',
                                    raise_if_not_found=False)
        return (default_user or self.env['res.users']).sudo().groups_id

    def _companies_count(self):
        return self.env['res.company'].sudo().search_count([])

    partner_id = fields.Many2one('res.partner',
                                 required=True,
                                 ondelete='restrict',
                                 auto_join=True,
                                 string='Related Partner',
                                 help='Partner-related data of the user')
    login = fields.Char(required=True, help="Used to log into the system")
    password = fields.Char(
        default='',
        invisible=True,
        copy=False,
        help=
        "Keep empty if you don't want the user to be able to connect on the system."
    )
    new_password = fields.Char(string='Set Password',
        compute='_compute_password', inverse='_inverse_password',
        help="Specify a value only when creating a user or if you're "\
             "changing the user's password, otherwise leave empty. After "\
             "a change of password, the user has to login again.")
    signature = fields.Html()
    active = fields.Boolean(default=True)
    action_id = fields.Many2one(
        'ir.actions.actions',
        string='Home Action',
        help=
        "If specified, this action will be opened at log on for this user, in addition to the standard menu."
    )
    groups_id = fields.Many2many('res.groups',
                                 'res_groups_users_rel',
                                 'uid',
                                 'gid',
                                 string='Groups',
                                 default=_default_groups)
    log_ids = fields.One2many('res.users.log',
                              'create_uid',
                              string='User log entries')
    login_date = fields.Datetime(related='log_ids.create_date',
                                 string='Latest connection')
    share = fields.Boolean(
        compute='_compute_share',
        compute_sudo=True,
        string='Share User',
        store=True,
        help=
        "External user with limited access, created only for the purpose of sharing data."
    )
    companies_count = fields.Integer(compute='_compute_companies_count',
                                     string="Number of Companies",
                                     default=_companies_count)
    tz_offset = fields.Char(compute='_compute_tz_offset',
                            string='Timezone offset',
                            invisible=True)

    @api.model
    def _get_company(self):
        return self.env.user.company_id

    # Special behavior for this field: res.company.search() will only return the companies
    # available to the current user (should be the user's companies?), when the user_preference
    # context is set.
    company_id = fields.Many2one(
        'res.company',
        string='Company',
        required=True,
        default=_get_company,
        help='The company this user is currently working for.',
        context={'user_preference': True})
    company_ids = fields.Many2many('res.company',
                                   'res_company_users_rel',
                                   'user_id',
                                   'cid',
                                   string='Companies',
                                   default=_get_company)

    # overridden inherited fields to bypass access rights, in case you have
    # access to the user but not its corresponding partner
    name = fields.Char(related='partner_id.name', inherited=True)
    email = fields.Char(related='partner_id.email', inherited=True)

    _sql_constraints = [('login_key', 'UNIQUE (login)',
                         'You can not have two users with the same login !')]

    def _compute_password(self):
        for user in self:
            user.password = ''

    def _inverse_password(self):
        for user in self:
            if not user.new_password:
                # Do not update the password if no value is provided, ignore silently.
                # For example web client submits False values for all empty fields.
                continue
            if user == self.env.user:
                # To change their own password, users must use the client-specific change password wizard,
                # so that the new password is immediately used for further RPC requests, otherwise the user
                # will face unexpected 'Access Denied' exceptions.
                raise UserError(
                    _('Please use the change password wizard (in User Preferences or User menu) to change your own password.'
                      ))
            else:
                user.password = user.new_password

    @api.depends('groups_id')
    def _compute_share(self):
        for user in self:
            user.share = not user.has_group('base.group_user')

    @api.multi
    def _compute_companies_count(self):
        companies_count = self._companies_count()
        for user in self:
            user.companies_count = companies_count

    @api.depends('tz')
    def _compute_tz_offset(self):
        for user in self:
            user.tz_offset = datetime.datetime.now(
                pytz.timezone(user.tz or 'GMT')).strftime('%z')

    @api.onchange('login')
    def on_change_login(self):
        if self.login and tools.single_email_re.match(self.login):
            self.email = self.login

    @api.onchange('parent_id')
    def onchange_parent_id(self):
        return self.mapped('partner_id').onchange_parent_id()

    @api.multi
    @api.constrains('company_id', 'company_ids')
    def _check_company(self):
        if any(user.company_ids and user.company_id not in user.company_ids
               for user in self):
            raise ValidationError(
                _('The chosen company is not in the allowed companies for this user'
                  ))

    @api.multi
    @api.constrains('action_id')
    def _check_action_id(self):
        action_open_website = self.env.ref('base.action_open_website',
                                           raise_if_not_found=False)
        if action_open_website and any(
                user.action_id.id == action_open_website.id for user in self):
            raise ValidationError(
                _('The "App Switcher" action cannot be selected as home action.'
                  ))

    @api.multi
    def read(self, fields=None, load='_classic_read'):
        if fields and self == self.env.user:
            for key in fields:
                if not (key in self.SELF_READABLE_FIELDS
                        or key.startswith('context_')):
                    break
            else:
                # safe fields only, so we read as super-user to bypass access rights
                self = self.sudo()

        result = super(Users, self).read(fields=fields, load=load)

        canwrite = self.env['ir.model.access'].check('res.users', 'write',
                                                     False)
        if not canwrite:
            for vals in result:
                if vals['id'] != self._uid:
                    for key in USER_PRIVATE_FIELDS:
                        if key in vals:
                            vals[key] = '********'

        return result

    @api.model
    def read_group(self,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   orderby=False,
                   lazy=True):
        groupby_fields = set([groupby] if isinstance(
            groupby, pycompat.string_types) else groupby)
        if groupby_fields.intersection(USER_PRIVATE_FIELDS):
            raise AccessError(_("Invalid 'group by' parameter"))
        return super(Users, self).read_group(domain,
                                             fields,
                                             groupby,
                                             offset=offset,
                                             limit=limit,
                                             orderby=orderby,
                                             lazy=lazy)

    @api.model
    def _search(self,
                args,
                offset=0,
                limit=None,
                order=None,
                count=False,
                access_rights_uid=None):
        if self._uid != SUPERUSER_ID and args:
            domain_fields = {
                term[0]
                for term in args if isinstance(term, (tuple, list))
            }
            if domain_fields.intersection(USER_PRIVATE_FIELDS):
                raise AccessError(_('Invalid search criterion'))
        return super(Users, self)._search(args,
                                          offset=offset,
                                          limit=limit,
                                          order=order,
                                          count=count,
                                          access_rights_uid=access_rights_uid)

    @api.model
    def create(self, vals):
        user = super(Users, self).create(vals)
        user.partner_id.active = user.active
        if user.partner_id.company_id:
            user.partner_id.write({'company_id': user.company_id.id})
        return user

    @api.multi
    def write(self, values):
        if values.get('active') == False:
            for user in self:
                if user.id == SUPERUSER_ID:
                    raise UserError(_("You cannot deactivate the admin user."))
                elif user.id == self._uid:
                    raise UserError(
                        _("You cannot deactivate the user you're currently logged in as."
                          ))

        if self == self.env.user:
            for key in list(values):
                if not (key in self.SELF_WRITEABLE_FIELDS
                        or key.startswith('context_')):
                    break
            else:
                if 'company_id' in values:
                    if values[
                            'company_id'] not in self.env.user.company_ids.ids:
                        del values['company_id']
                # safe fields only, so we write as super-user to bypass access rights
                self = self.sudo()

        res = super(Users, self).write(values)
        if 'company_id' in values:
            for user in self:
                # if partner is global we keep it that way
                if user.partner_id.company_id and user.partner_id.company_id.id != values[
                        'company_id']:
                    user.partner_id.write({'company_id': user.company_id.id})
            # clear default ir values when company changes
            self.env['ir.default'].clear_caches()

        # clear caches linked to the users
        if 'groups_id' in values:
            self.env['ir.model.access'].call_cache_clearing_methods()
            self.env['ir.rule'].clear_caches()
            self.has_group.clear_cache(self)
        if any(
                key.startswith('context_') or key in ('lang', 'tz')
                for key in values):
            self.context_get.clear_cache(self)
        if any(key in values for key in ['active'] + USER_PRIVATE_FIELDS):
            db = self._cr.dbname
            for id in self.ids:
                self.__uid_cache[db].pop(id, None)
        if any(key in values for key in self._get_session_token_fields()):
            self._invalidate_session_cache()

        return res

    @api.multi
    def unlink(self):
        if SUPERUSER_ID in self.ids:
            raise UserError(
                _('You can not remove the admin user as it is used internally for resources created by Flectra (updates, module installation, ...)'
                  ))
        db = self._cr.dbname
        for id in self.ids:
            self.__uid_cache[db].pop(id, None)
        self._invalidate_session_cache()
        return super(Users, self).unlink()

    @api.model
    def name_search(self, name='', args=None, operator='ilike', limit=100):
        if args is None:
            args = []
        users = self.browse()
        if name and operator in ['=', 'ilike']:
            users = self.search([('login', '=', name)] + args, limit=limit)
        if not users:
            users = self.search([('name', operator, name)] + args, limit=limit)
        return users.name_get()

    @api.multi
    def copy(self, default=None):
        self.ensure_one()
        default = dict(default or {})
        if ('name' not in default) and ('partner_id' not in default):
            default['name'] = _("%s (copy)") % self.name
        if 'login' not in default:
            default['login'] = _("%s (copy)") % self.login
        return super(Users, self).copy(default)

    @api.model
    @tools.ormcache('self._uid')
    def context_get(self):
        user = self.env.user
        result = {}
        for k in self._fields:
            if k.startswith('context_'):
                context_key = k[8:]
            elif k in ['lang', 'tz']:
                context_key = k
            else:
                context_key = False
            if context_key:
                res = getattr(user, k) or False
                if isinstance(res, models.BaseModel):
                    res = res.id
                result[context_key] = res or False
        return result

    @api.model
    @api.returns('ir.actions.act_window', lambda record: record.id)
    def action_get(self):
        return self.sudo().env.ref('base.action_res_users_my')

    def check_super(self, passwd):
        return check_super(passwd)

    @api.model
    def check_credentials(self, password):
        """ Override this method to plug additional authentication methods"""
        user = self.sudo().search([('id', '=', self._uid),
                                   ('password', '=', password)])
        if not user:
            raise AccessDenied()

    @api.model
    def _update_last_login(self):
        # only create new records to avoid any side-effect on concurrent transactions
        # extra records will be deleted by the periodical garbage collection
        self.env['res.users.log'].create({})  # populated by defaults

    @classmethod
    def _login(cls, db, login, password):
        if not password:
            return False
        user_id = False
        try:
            with cls.pool.cursor() as cr:
                self = api.Environment(cr, SUPERUSER_ID, {})[cls._name]
                user = self.search([('login', '=', login)])
                if user:
                    user_id = user.id
                    user.sudo(user_id).check_credentials(password)
                    user.sudo(user_id)._update_last_login()
        except AccessDenied:
            user_id = False

        status = "successful" if user_id else "failed"
        ip = request.httprequest.environ['REMOTE_ADDR'] if request else 'n/a'
        _logger.info("Login %s for db:%s login:%s from %s", status, db, login,
                     ip)

        return user_id

    @classmethod
    def authenticate(cls, db, login, password, user_agent_env):
        """Verifies and returns the user ID corresponding to the given
          ``login`` and ``password`` combination, or False if there was
          no matching user.
           :param str db: the database on which user is trying to authenticate
           :param str login: username
           :param str password: user password
           :param dict user_agent_env: environment dictionary describing any
               relevant environment attributes
        """
        uid = cls._login(db, login, password)
        if uid == SUPERUSER_ID:
            # Successfully logged in as admin!
            # Attempt to guess the web base url...
            if user_agent_env and user_agent_env.get('base_location'):
                try:
                    with cls.pool.cursor() as cr:
                        base = user_agent_env['base_location']
                        ICP = api.Environment(cr, uid,
                                              {})['ir.config_parameter']
                        if not ICP.get_param('web.base.url.freeze'):
                            ICP.set_param('web.base.url', base)
                except Exception:
                    _logger.exception(
                        "Failed to update web.base.url configuration parameter"
                    )
        return uid

    @classmethod
    def check(cls, db, uid, passwd):
        """Verifies that the given (uid, password) is authorized for the database ``db`` and
           raise an exception if it is not."""
        if not passwd:
            # empty passwords disallowed for obvious security reasons
            raise AccessDenied()
        db = cls.pool.db_name
        if cls.__uid_cache[db].get(uid) == passwd:
            return
        cr = cls.pool.cursor()
        try:
            self = api.Environment(cr, uid, {})[cls._name]
            self.check_credentials(passwd)
            cls.__uid_cache[db][uid] = passwd
        finally:
            cr.close()

    def _get_session_token_fields(self):
        return {'id', 'login', 'password', 'active'}

    @tools.ormcache('sid')
    def _compute_session_token(self, sid):
        """ Compute a session token given a session id and a user id """
        # retrieve the fields used to generate the session token
        session_fields = ', '.join(sorted(self._get_session_token_fields()))
        self.env.cr.execute(
            """SELECT %s, (SELECT value FROM ir_config_parameter WHERE key='database.secret')
                                FROM res_users
                                WHERE id=%%s""" % (session_fields),
            (self.id, ))
        if self.env.cr.rowcount != 1:
            self._invalidate_session_cache()
            return False
        data_fields = self.env.cr.fetchone()
        # generate hmac key
        key = (u'%s' % (data_fields, )).encode('utf-8')
        # hmac the session id
        data = sid.encode('utf-8')
        h = hmac.new(key, data, sha256)
        # keep in the cache the token
        return h.hexdigest()

    @api.multi
    def _invalidate_session_cache(self):
        """ Clear the sessions cache """
        self._compute_session_token.clear_cache(self)

    @api.model
    def change_password(self, old_passwd, new_passwd):
        """Change current user password. Old password must be provided explicitly
        to prevent hijacking an existing user session, or for cases where the cleartext
        password is not used to authenticate requests.

        :return: True
        :raise: flectra.exceptions.AccessDenied when old password is wrong
        :raise: flectra.exceptions.UserError when new password is not set or empty
        """
        self.check(self._cr.dbname, self._uid, old_passwd)
        if new_passwd:
            # use self.env.user here, because it has uid=SUPERUSER_ID
            return self.env.user.write({'password': new_passwd})
        raise UserError(
            _("Setting empty passwords is not allowed for security reasons!"))

    @api.multi
    def preference_save(self):
        return {
            'type': 'ir.actions.client',
            'tag': 'reload_context',
        }

    @api.multi
    def preference_change_password(self):
        return {
            'type': 'ir.actions.client',
            'tag': 'change_password',
            'target': 'new',
        }

    @api.model
    def has_group(self, group_ext_id):
        # use singleton's id if called on a non-empty recordset, otherwise
        # context uid
        uid = self.id or self._uid
        return self.sudo(user=uid)._has_group(group_ext_id)

    @api.model
    @tools.ormcache('self._uid', 'group_ext_id')
    def _has_group(self, group_ext_id):
        """Checks whether user belongs to given group.

        :param str group_ext_id: external ID (XML ID) of the group.
           Must be provided in fully-qualified form (``module.ext_id``), as there
           is no implicit module to use..
        :return: True if the current user is a member of the group with the
           given external ID (XML ID), else False.
        """
        assert group_ext_id and '.' in group_ext_id, "External ID must be fully qualified"
        module, ext_id = group_ext_id.split('.')
        self._cr.execute(
            """SELECT 1 FROM res_groups_users_rel WHERE uid=%s AND gid IN
                            (SELECT res_id FROM ir_model_data WHERE module=%s AND name=%s)""",
            (self._uid, module, ext_id))
        return bool(self._cr.fetchone())

    # for a few places explicitly clearing the has_group cache
    has_group.clear_cache = _has_group.clear_cache

    @api.multi
    def _is_public(self):
        self.ensure_one()
        return self.has_group('base.group_public')

    @api.multi
    def _is_system(self):
        self.ensure_one()
        return self.has_group('base.group_system')

    @api.multi
    def _is_admin(self):
        self.ensure_one()
        return self._is_superuser() or self.has_group('base.group_erp_manager')

    @api.multi
    def _is_superuser(self):
        self.ensure_one()
        return self.id == SUPERUSER_ID

    @api.model
    def get_company_currency_id(self):
        return self.env.user.company_id.currency_id.id
コード例 #3
0
class Contract(models.Model):

    _name = 'hr.contract'
    _description = 'Contract'
    _inherit = ['mail.thread']

    name = fields.Char('Contract Reference', required=True)
    employee_id = fields.Many2one('hr.employee', string='Employee')
    department_id = fields.Many2one('hr.department', string="Department")
    type_id = fields.Many2one('hr.contract.type', string="Contract Type", required=True, default=lambda self: self.env['hr.contract.type'].search([], limit=1))
    job_id = fields.Many2one('hr.job', string='Job Position')
    date_start = fields.Date('Start Date', required=True, default=fields.Date.today,
        help="Start date of the contract.")
    date_end = fields.Date('End Date',
        help="End date of the contract (if it's a fixed-term contract).")
    trial_date_end = fields.Date('End of Trial Period',
        help="End date of the trial period (if there is one).")
    resource_calendar_id = fields.Many2one(
        'resource.calendar', 'Working Schedule',
        default=lambda self: self.env['res.company']._company_default_get().resource_calendar_id.id)
    wage = fields.Monetary('Wage', digits=(16, 2), required=True, help="Employee's monthly gross wage.")
    advantages = fields.Text('Advantages')
    notes = fields.Text('Notes')
    state = fields.Selection([
        ('draft', 'New'),
        ('open', 'Running'),
        ('pending', 'To Renew'),
        ('close', 'Expired'),
        ('cancel', 'Cancelled')
    ], string='Status', group_expand='_expand_states',
       track_visibility='onchange', help='Status of the contract', default='draft')
    company_id = fields.Many2one('res.company', default=lambda self: self.env.user.company_id)
    currency_id = fields.Many2one(string="Currency", related='company_id.currency_id', readonly=True)
    permit_no = fields.Char('Work Permit No', related="employee_id.permit_no")
    visa_no = fields.Char('Visa No', related="employee_id.visa_no")
    visa_expire = fields.Date('Visa Expire Date', related="employee_id.visa_expire")

    def _expand_states(self, states, domain, order):
        return [key for key, val in type(self).state.selection]

    @api.onchange('employee_id')
    def _onchange_employee_id(self):
        if self.employee_id:
            self.job_id = self.employee_id.job_id
            self.department_id = self.employee_id.department_id
            self.resource_calendar_id = self.employee_id.resource_calendar_id

    @api.constrains('date_start', 'date_end')
    def _check_dates(self):
        if self.filtered(lambda c: c.date_end and c.date_start > c.date_end):
            raise ValidationError(_('Contract start date must be less than contract end date.'))

    @api.model
    def update_state(self):
        self.search([
            ('state', '=', 'open'),
            '|',
            '&',
            ('date_end', '<=', fields.Date.to_string(date.today() + relativedelta(days=7))),
            ('date_end', '>=', fields.Date.to_string(date.today() + relativedelta(days=1))),
            '&',
            ('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=60))),
            ('visa_expire', '>=', fields.Date.to_string(date.today() + relativedelta(days=1))),
        ]).write({
            'state': 'pending'
        })

        self.search([
            ('state', 'in', ('open', 'pending')),
            '|',
            ('date_end', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))),
            ('visa_expire', '<=', fields.Date.to_string(date.today() + relativedelta(days=1))),
        ]).write({
            'state': 'close'
        })

        return True

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'state' in init_values and self.state == 'pending':
            return 'hr_contract.mt_contract_pending'
        elif 'state' in init_values and self.state == 'close':
            return 'hr_contract.mt_contract_close'
        return super(Contract, self)._track_subtype(init_values)
コード例 #4
0
class AccountInvoiceReport(models.Model):
    _name = "account.invoice.report"
    _inherit = ['ir.branch.company.mixin']
    _description = "Invoices Statistics"
    _auto = False
    _rec_name = 'date'

    @api.multi
    @api.depends('currency_id', 'date', 'price_total', 'price_average',
                 'residual')
    def _compute_amounts_in_user_currency(self):
        """Compute the amounts in the currency of the user
        """
        context = dict(self._context or {})
        user_currency_id = self.env.user.company_id.currency_id
        currency_rate_id = self.env['res.currency.rate'].search(
            [('rate', '=', 1), '|',
             ('company_id', '=', self.env.user.company_id.id),
             ('company_id', '=', False)],
            limit=1)
        base_currency_id = currency_rate_id.currency_id
        ctx = context.copy()
        for record in self:
            ctx['date'] = record.date
            record.user_currency_price_total = base_currency_id.with_context(
                ctx).compute(record.price_total, user_currency_id)
            record.user_currency_price_average = base_currency_id.with_context(
                ctx).compute(record.price_average, user_currency_id)
            record.user_currency_residual = base_currency_id.with_context(
                ctx).compute(record.residual, user_currency_id)

    date = fields.Date(readonly=True)
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 readonly=True)
    product_qty = fields.Float(string='Product Quantity', readonly=True)
    uom_name = fields.Char(string='Reference Unit of Measure', readonly=True)
    payment_term_id = fields.Many2one('account.payment.term',
                                      string='Payment Terms',
                                      oldname='payment_term',
                                      readonly=True)
    fiscal_position_id = fields.Many2one('account.fiscal.position',
                                         oldname='fiscal_position',
                                         string='Fiscal Position',
                                         readonly=True)
    currency_id = fields.Many2one('res.currency',
                                  string='Currency',
                                  readonly=True)
    categ_id = fields.Many2one('product.category',
                               string='Product Category',
                               readonly=True)
    journal_id = fields.Many2one('account.journal',
                                 string='Journal',
                                 readonly=True)
    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 readonly=True)
    commercial_partner_id = fields.Many2one('res.partner',
                                            string='Partner Company',
                                            help="Commercial Entity")
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True)
    user_id = fields.Many2one('res.users', string='Salesperson', readonly=True)
    price_total = fields.Float(string='Total Without Tax', readonly=True)
    user_currency_price_total = fields.Float(
        string="Total Without Tax",
        compute='_compute_amounts_in_user_currency',
        digits=0)
    price_average = fields.Float(string='Average Price',
                                 readonly=True,
                                 group_operator="avg")
    user_currency_price_average = fields.Float(
        string="Average Price",
        compute='_compute_amounts_in_user_currency',
        digits=0)
    currency_rate = fields.Float(string='Currency Rate',
                                 readonly=True,
                                 group_operator="avg",
                                 groups="base.group_multi_currency")
    nbr = fields.Integer(
        string='# of Lines',
        readonly=True)  # TDE FIXME master: rename into nbr_lines
    type = fields.Selection([
        ('out_invoice', 'Customer Invoice'),
        ('in_invoice', 'Vendor Bill'),
        ('out_refund', 'Customer Credit Note'),
        ('in_refund', 'Vendor Credit Note'),
    ],
                            readonly=True)
    state = fields.Selection([('draft', 'Draft'), ('open', 'Open'),
                              ('paid', 'Paid'), ('cancel', 'Cancelled')],
                             string='Invoice Status',
                             readonly=True)
    date_due = fields.Date(string='Due Date', readonly=True)
    account_id = fields.Many2one('account.account',
                                 string='Account',
                                 readonly=True,
                                 domain=[('deprecated', '=', False)])
    account_line_id = fields.Many2one('account.account',
                                      string='Account Line',
                                      readonly=True,
                                      domain=[('deprecated', '=', False)])
    partner_bank_id = fields.Many2one('res.partner.bank',
                                      string='Bank Account',
                                      readonly=True)
    residual = fields.Float(string='Due Amount', readonly=True)
    user_currency_residual = fields.Float(
        string="Total Residual",
        compute='_compute_amounts_in_user_currency',
        digits=0)
    country_id = fields.Many2one('res.country',
                                 string='Country of the Partner Company')
    account_analytic_id = fields.Many2one(
        'account.analytic.account',
        string='Analytic Account',
        groups="analytic.group_analytic_accounting")

    _order = 'date desc'

    _depends = {
        'account.invoice': [
            'account_id',
            'amount_total_company_signed',
            'commercial_partner_id',
            'company_id',
            'branch_id',
            'currency_id',
            'date_due',
            'date_invoice',
            'fiscal_position_id',
            'journal_id',
            'partner_bank_id',
            'partner_id',
            'payment_term_id',
            'residual',
            'state',
            'type',
            'user_id',
        ],
        'account.invoice.line': [
            'account_id',
            'invoice_id',
            'price_subtotal',
            'product_id',
            'quantity',
            'uom_id',
            'account_analytic_id',
        ],
        'product.product': ['product_tmpl_id'],
        'product.template': ['categ_id'],
        'product.uom': ['category_id', 'factor', 'name', 'uom_type'],
        'res.currency.rate': ['currency_id', 'name'],
        'res.partner': ['country_id'],
    }

    def _select(self):
        select_str = """
            SELECT sub.id, sub.date, sub.product_id, sub.partner_id, sub.country_id, sub.account_analytic_id,
                sub.payment_term_id, sub.uom_name, sub.currency_id, sub.journal_id,
                sub.fiscal_position_id, sub.user_id, sub.company_id, sub.branch_id, sub.nbr, sub.type, sub.state,
                sub.categ_id, sub.date_due, sub.account_id, sub.account_line_id, sub.partner_bank_id,
                sub.product_qty, sub.price_total as price_total, sub.price_average as price_average,
                COALESCE(cr.rate, 1) as currency_rate, sub.residual as residual, sub.commercial_partner_id as commercial_partner_id
        """
        return select_str

    def _sub_select(self):
        select_str = """
                SELECT ail.id AS id,
                    ai.date_invoice AS date,
                    ail.product_id, ai.partner_id, ai.payment_term_id, ail.account_analytic_id,
                    u2.name AS uom_name,
                    ai.currency_id, ai.journal_id, ai.fiscal_position_id, ai.user_id, ai.company_id, ai.branch_id,
                    1 AS nbr,
                    ai.type, ai.state, pt.categ_id, ai.date_due, ai.account_id, ail.account_id AS account_line_id,
                    ai.partner_bank_id,
                    SUM ((invoice_type.sign * ail.quantity) / u.factor * u2.factor) AS product_qty,
                    SUM(ail.price_subtotal_signed * invoice_type.sign) AS price_total,
                    SUM(ABS(ail.price_subtotal_signed)) / CASE
                            WHEN SUM(ail.quantity / u.factor * u2.factor) <> 0::numeric
                               THEN SUM(ail.quantity / u.factor * u2.factor)
                               ELSE 1::numeric
                            END AS price_average,
                    ai.residual_company_signed / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
                    count(*) * invoice_type.sign AS residual,
                    ai.commercial_partner_id as commercial_partner_id,
                    partner.country_id
        """
        return select_str

    def _from(self):
        from_str = """
                FROM account_invoice_line ail
                JOIN account_invoice ai ON ai.id = ail.invoice_id
                JOIN res_partner partner ON ai.commercial_partner_id = partner.id
                LEFT JOIN product_product pr ON pr.id = ail.product_id
                left JOIN product_template pt ON pt.id = pr.product_tmpl_id
                LEFT JOIN product_uom u ON u.id = ail.uom_id
                LEFT JOIN product_uom u2 ON u2.id = pt.uom_id
                JOIN (
                    -- Temporary table to decide if the qty should be added or retrieved (Invoice vs Credit Note)
                    SELECT id,(CASE
                         WHEN ai.type::text = ANY (ARRAY['in_refund'::character varying::text, 'in_invoice'::character varying::text])
                            THEN -1
                            ELSE 1
                        END) AS sign
                    FROM account_invoice ai
                ) AS invoice_type ON invoice_type.id = ai.id
        """
        return from_str

    def _group_by(self):
        group_by_str = """
                GROUP BY ail.id, ail.product_id, ail.account_analytic_id, ai.date_invoice, ai.id,
                    ai.partner_id, ai.payment_term_id, u2.name, u2.id, ai.currency_id, ai.journal_id,
                    ai.fiscal_position_id, ai.user_id, ai.company_id, ai.branch_id, ai.type, invoice_type.sign, ai.state, pt.categ_id,
                    ai.date_due, ai.account_id, ail.account_id, ai.partner_bank_id, ai.residual_company_signed,
                    ai.amount_total_company_signed, ai.commercial_partner_id, partner.country_id
        """
        return group_by_str

    @api.model_cr
    def init(self):
        # self._table = account_invoice_report
        tools.drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute(
            """CREATE or REPLACE VIEW %s as (
            WITH currency_rate AS (%s)
            %s
            FROM (
                %s %s %s
            ) AS sub
            LEFT JOIN currency_rate cr ON
                (cr.currency_id = sub.currency_id AND
                 cr.company_id = sub.company_id AND
                 cr.date_start <= COALESCE(sub.date, NOW()) AND
                 (cr.date_end IS NULL OR cr.date_end > COALESCE(sub.date, NOW())))
        )""" %
            (self._table, self.env['res.currency']._select_companies_rates(),
             self._select(), self._sub_select(), self._from(),
             self._group_by()))
コード例 #5
0
ファイル: res_users.py プロジェクト: mohshedid/odoo10_plus
class Users(models.Model):
    """ Update of res.users class
        - add a preference about sending emails about notifications
        - make a new user follow itself
        - add a welcome message
        - add suggestion preference
        - if adding groups to an user, check mail.channels linked to this user
          group, and the user. This is done by overriding the write method.
    """
    _name = 'res.users'
    _inherit = ['res.users']

    alias_id = fields.Many2one('mail.alias', 'Alias', ondelete="set null", required=False,
            help="Email address internally associated with this user. Incoming "\
                 "emails will appear in the user's notifications.", copy=False, auto_join=True)
    alias_contact = fields.Selection([
        ('everyone', 'Everyone'),
        ('partners', 'Authenticated Partners'),
        ('followers', 'Followers only')], string='Alias Contact Security', related='alias_id.alias_contact')
    notification_type = fields.Selection([
        ('email', 'Handle by Emails'),
        ('inbox', 'Handle in Flectra')],
        'Notification Management', required=True, default='email',
        help="Policy on how to handle Chatter notifications:\n"
             "- Emails: notifications are sent to your email\n"
             "- Flectra: notifications appear in your Flectra Inbox")

    def __init__(self, pool, cr):
        """ Override of __init__ to add access rights on notification_email_send
            and alias fields. Access rights are disabled by default, but allowed
            on some specific fields defined in self.SELF_{READ/WRITE}ABLE_FIELDS.
        """
        init_res = super(Users, self).__init__(pool, cr)
        # duplicate list to avoid modifying the original reference
        type(self).SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
        type(self).SELF_WRITEABLE_FIELDS.extend(['notification_type'])
        # duplicate list to avoid modifying the original reference
        type(self).SELF_READABLE_FIELDS = list(self.SELF_READABLE_FIELDS)
        type(self).SELF_READABLE_FIELDS.extend(['notification_type'])
        return init_res

    @api.model
    def create(self, values):
        if not values.get('login', False):
            action = self.env.ref('base.action_res_users')
            msg = _("You cannot create a new user from here.\n To create new user please go to configuration panel.")
            raise exceptions.RedirectWarning(msg, action.id, _('Go to the configuration panel'))

        user = super(Users, self).create(values)

        # create a welcome message
        user._create_welcome_message()
        return user

    @api.multi
    def write(self, vals):
        write_res = super(Users, self).write(vals)
        if vals.get('groups_id'):
            # form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
            user_group_ids = [command[1] for command in vals['groups_id'] if command[0] == 4]
            user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
            self.env['mail.channel'].search([('group_ids', 'in', user_group_ids)])._subscribe_users()
        return write_res

    def _create_welcome_message(self):
        self.ensure_one()
        if not self.has_group('base.group_user'):
            return False
        company_name = self.company_id.name if self.company_id else ''
        body = _('%s has joined the %s network.') % (self.name, company_name)
        # TODO change SUPERUSER_ID into user.id but catch errors
        return self.partner_id.sudo().message_post(body=body)

    def _message_post_get_pid(self):
        self.ensure_one()
        if 'thread_model' in self.env.context:
            self = self.with_context(thread_model='res.users')
        return self.partner_id.id

    @api.multi
    @api.returns('self', lambda value: value.id)
    def message_post(self, **kwargs):
        """ Redirect the posting of message on res.users as a private discussion.
            This is done because when giving the context of Chatter on the
            various mailboxes, we do not have access to the current partner_id. """
        current_pids = []
        partner_ids = kwargs.get('partner_ids', [])
        user_pid = self._message_post_get_pid()
        for partner_id in partner_ids:
            if isinstance(partner_id, (list, tuple)) and partner_id[0] == 4 and len(partner_id) == 2:
                current_pids.append(partner_id[1])
            elif isinstance(partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3:
                current_pids.append(partner_id[2])
            elif isinstance(partner_id, pycompat.integer_types):
                current_pids.append(partner_id)
        if user_pid not in current_pids:
            partner_ids.append(user_pid)
        kwargs['partner_ids'] = partner_ids
        return self.env['mail.thread'].message_post(**kwargs)

    def message_update(self, msg_dict, update_vals=None):
        return True

    def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True):
        return True

    @api.multi
    def message_partner_info_from_emails(self, emails, link_mail=False):
        return self.env['mail.thread'].message_partner_info_from_emails(emails, link_mail=link_mail)

    @api.multi
    def message_get_suggested_recipients(self):
        return dict((res_id, list()) for res_id in self._ids)

    @api.model
    def activity_user_count(self):
        query = """SELECT m.name, count(*), act.res_model as model,
                        CASE
                            WHEN now()::date - act.date_deadline::date = 0 Then 'today'
                            WHEN now()::date - act.date_deadline::date > 0 Then 'overdue'
                            WHEN now()::date - act.date_deadline::date < 0 Then 'planned'
                        END AS states
                    FROM mail_activity AS act
                    JOIN ir_model AS m ON act.res_model_id = m.id
                    WHERE user_id = %s
                    GROUP BY m.name, states, act.res_model;
                    """
        self.env.cr.execute(query, [self.env.uid])
        activity_data = self.env.cr.dictfetchall()

        user_activities = {}
        for activity in activity_data:
            if not user_activities.get(activity['model']):
                user_activities[activity['model']] = {
                    'name': activity['name'],
                    'model': activity['model'],
                    'icon': modules.module.get_module_icon(self.env[activity['model']]._original_module),
                    'total_count': 0, 'today_count': 0, 'overdue_count': 0, 'planned_count': 0,
                }
            user_activities[activity['model']]['%s_count' % activity['states']] += activity['count']
            if activity['states'] in ('today','overdue'):
                user_activities[activity['model']]['total_count'] += activity['count']

        return list(user_activities.values())
コード例 #6
0
class AccountInvoiceLine(models.Model):
    _inherit = 'account.move.line'

    asset_category_id = fields.Many2one('account.asset.category',
                                        string='Asset Category')
    asset_start_date = fields.Date(string='Asset Start Date',
                                   compute='_get_asset_date',
                                   readonly=True,
                                   store=True)
    asset_end_date = fields.Date(string='Asset End Date',
                                 compute='_get_asset_date',
                                 readonly=True,
                                 store=True)
    asset_mrr = fields.Float(string='Monthly Recurring Revenue',
                             compute='_get_asset_date',
                             readonly=True,
                             digits="Account",
                             store=True)

    @api.depends('asset_category_id', 'move_id.invoice_date')
    def _get_asset_date(self):
        for rec in self:
            rec.asset_mrr = 0
            rec.asset_start_date = False
            rec.asset_end_date = False
            cat = rec.asset_category_id
            if cat:
                if cat.method_number == 0 or cat.method_period == 0:
                    raise UserError(
                        _('The number of depreciations or the period length of '
                          'your asset category cannot be 0.'))
                months = cat.method_number * cat.method_period
                if rec.move_id.move_type in ['out_invoice', 'out_refund']:
                    rec.asset_mrr = rec.price_subtotal / months
                if rec.move_id.invoice_date:
                    start_date = rec.move_id.invoice_date.replace(day=1)
                    end_date = (start_date +
                                relativedelta(months=months, days=-1))
                    rec.asset_start_date = start_date
                    rec.asset_end_date = end_date

    def asset_create(self):
        if self.asset_category_id:
            vals = {
                'name': self.name,
                'code': self.name or False,
                'category_id': self.asset_category_id.id,
                'value': self.price_subtotal,
                'partner_id': self.move_id.partner_id.id,
                'company_id': self.move_id.company_id.id,
                'currency_id': self.move_id.company_currency_id.id,
                'date': self.move_id.invoice_date,
                'invoice_id': self.move_id.id,
            }
            changed_vals = self.env[
                'account.asset.asset'].onchange_category_id_values(
                    vals['category_id'])
            vals.update(changed_vals['value'])
            asset = self.env['account.asset.asset'].create(vals)
            if self.asset_category_id.open_asset:
                asset.validate()
        return True

    @api.onchange('asset_category_id')
    def onchange_asset_category_id(self):
        if self.move_id.move_type == 'out_invoice' and self.asset_category_id:
            self.account_id = self.asset_category_id.account_asset_id.id
        elif self.move_id.move_type == 'in_invoice' and self.asset_category_id:
            self.account_id = self.asset_category_id.account_asset_id.id

    @api.onchange('uom_id')
    def _onchange_uom_id(self):
        result = super(AccountInvoiceLine, self)._onchange_uom_id()
        self.onchange_asset_category_id()
        return result

    @api.onchange('product_id')
    def _onchange_product_id(self):
        vals = super(AccountInvoiceLine, self)._onchange_product_id()
        if self.product_id:
            for rec in self:
                if rec.move_id.move_type == 'out_invoice':
                    rec.asset_category_id = rec.product_id.product_tmpl_id.deferred_revenue_category_id
                elif rec.move_id.move_type == 'in_invoice':
                    rec.asset_category_id = rec.product_id.product_tmpl_id.asset_category_id
        return vals

    def _set_additional_fields(self, invoice):
        if not self.asset_category_id:
            if invoice.type == 'out_invoice':
                self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id.id
            elif invoice.type == 'in_invoice':
                self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id.id
            self.onchange_asset_category_id()
        super(AccountInvoiceLine, self)._set_additional_fields(invoice)

    def get_invoice_line_account(self, type, product, fpos, company):
        return product.asset_category_id.account_asset_id or super(
            AccountInvoiceLine, self).get_invoice_line_account(
                type, product, fpos, company)
コード例 #7
0
ファイル: res_partner.py プロジェクト: mohshedid/odoo10_plus
class ResPartner(models.Model):
    _inherit = 'res.partner'

    contact_status_id = fields.Many2one('crm.contacts.status',
                                        'Contact Status')
    contact_type_id = fields.Many2one('crm.contacts.type', 'Contact Type')
コード例 #8
0
ファイル: user_signature_key.py プロジェクト: sc4you/perpul
class userSignature(models.Model):
    # _name = 'user.signature.key'
    _inherit = 'res.users'

    def default_status(self):
        return 'unverified'

    def load_cert_m2pem(self, *args, **kwargs):
        filecontent = base64.b64decode(self.key_file)
        cert = M2X509.load_cert(filecontent)
        issuer = cert.get_issuer()
        subject = cert.get_subject()

    def load_cert_pk12(self, filecontent):
        p12 = crypto.load_pkcs12(filecontent, self.dec_pass)

        cert = p12.get_certificate()
        privky = p12.get_privatekey()
        cacert = p12.get_ca_certificates()
        issuer = cert.get_issuer()
        subject = cert.get_subject()

        self.not_before = datetime.datetime.strptime(cert.get_notBefore().decode("utf-8"), '%Y%m%d%H%M%SZ')
        self.not_after = datetime.datetime.strptime(cert.get_notAfter().decode("utf-8"), '%Y%m%d%H%M%SZ')

        # self.final_date =
        self.subject_c = subject.C
        self.subject_title = subject.title
        self.subject_common_name = subject.CN
        self.subject_serial_number = subject.serialNumber
        self.subject_email_address = subject.emailAddress

        self.issuer_country = issuer.C
        self.issuer_organization = issuer.O
        self.issuer_common_name = issuer.CN
        self.issuer_serial_number = issuer.serialNumber
        self.issuer_email_address = issuer.emailAddress
        self.status = 'expired' if cert.has_expired() else 'valid'

        self.cert_serial_number = cert.get_serial_number()
        self.cert_signature_algor = cert.get_signature_algorithm()
        self.cert_version  = cert.get_version()
        self.cert_hash = cert.subject_name_hash()

        # data privada
        self.private_key_bits = privky.bits()
        self.private_key_check = privky.check()
        self.private_key_type = privky.type()
        # self.cacert = cacert

        certificate = p12.get_certificate()
        private_key = p12.get_privatekey()

        self.priv_key = crypto.dump_privatekey(type_, private_key)
        self.cert = crypto.dump_certificate(type_, certificate)

        pubkey = cert.get_pubkey()


    filename = fields.Char(string='File Name')
    key_file = fields.Binary(
        string='Signature File', required=False, store=True,
        help='Upload the Signature File')
    dec_pass = fields.Char(string='Pasword')
    # vigencia y estado
    not_before = fields.Date(
        string='Not Before', help='Not Before this Date', readonly=True)
    not_after = fields.Date(
        string='Not After', help='Not After this Date', readonly=True)
    status = fields.Selection(
        [('unverified', 'Unverified'), ('valid', 'Valid'), ('expired', 'Expired')],
        string='Status', default=default_status,
        help='''Draft: means it has not been checked yet.\nYou must press the\
"check" button.''')
    final_date = fields.Date(
        string='Last Date', help='Last Control Date', readonly=True)
    # sujeto
    subject_title = fields.Char(string='Subject Title', readonly=True)
    subject_c = fields.Char(string='Subject Country', readonly=True)
    subject_serial_number = fields.Char(
        string='Subject Serial Number')
    subject_common_name = fields.Char(
        string='Subject Common Name', readonly=True)
    subject_email_address = fields.Char(
        string='Subject Email Address', readonly=True)
    # emisor
    issuer_country = fields.Char(string='Issuer Country', readonly=True)
    issuer_serial_number = fields.Char(
        string='Issuer Serial Number', readonly=True)
    issuer_common_name = fields.Char(
        string='Issuer Common Name', readonly=True)
    issuer_email_address = fields.Char(
        string='Issuer Email Address', readonly=True)
    issuer_organization = fields.Char(
        string='Issuer Organization', readonly=True)
    # data del certificado
    cert_serial_number = fields.Char(string='Serial Number', readonly=True)
    cert_signature_algor = fields.Char(string='Signature Algorithm', readonly=True)
    cert_version  = fields.Char(string='Version', readonly=True)
    cert_hash = fields.Char(string='Hash', readonly=True)
    # data privad, readonly=Truea
    private_key_bits = fields.Char(string='Private Key Bits', readonly=True)
    private_key_check = fields.Char(string='Private Key Check', readonly=True)
    private_key_type = fields.Char(string='Private Key Type', readonly=True)
    # cacert = fields.Char('CA Cert', readonly=True)
    cert = fields.Text(string='Certificate', readonly=True)
    priv_key = fields.Text('Private Key', readonly=True)
    authorized_users_ids = fields.One2many('res.users','cert_owner_id',
                                           string='Authorized Users')
    cert_owner_id = fields.Many2one('res.users', string='Certificate Owner',
                                    select=True, ondelete='cascade')

    @api.multi
    def action_clean1(self):
        self.ensure_one()
        # todo: debe lanzar un wizard que confirme si se limpia o no
        # self.status = 'unverified'
        self.write(zero_values)


    @api.multi
    def action_process(self):
        self.ensure_one()
        filecontent = base64.b64decode(self.key_file)
        self.load_cert_pk12(filecontent)

    @api.multi
    @api.depends('key_file')
    def _get_date(self):
        self.ensure_one()
        old_date = self.issued_date
        if self.key_file != None and self.status == 'unverified':
            print(self.key_file)
            self.issued_date = fields.datetime.now()
        else:
            print('valor antiguo de fecha')
            print(old_date)
            self.issued_date = old_date
コード例 #9
0
ファイル: feeds.py プロジェクト: mulaudzicalvin/perpul
class WkFeed(models.Model):
    _name = "wk.feed"
    sequence = fields.Char(string='Sequence', )
    active = fields.Boolean(string='Active', default=1)
    name = fields.Char(string='Name', )
    __last_update = fields.Datetime(string='Last Modified on')
    state = fields.Selection(
        selection=State,
        string='Update Required',
        default='draft',
        copy=False,
    )
    channel_id = fields.Many2one('multi.channel.sale',
                                 string='Instance',
                                 domain=[('state', '=', 'validate')])
    channel = fields.Selection(
        related='channel_id.channel',
        string="Channel",
    )
    store_id = fields.Char(string='Store ID', )
    store_source = fields.Char(string='Store Source', )
    message = fields.Html(
        string='Message',
        copy=False,
    )

    @api.model
    def get_product_fields(self):
        return ProductFields

    @api.model
    def get_category_fields(self):
        return CategoryFields

    @api.multi
    def open_mapping_view(self):
        self.ensure_one()
        res_model = self._context.get('mapping_model')
        store_field = self._context.get('store_field')
        duplicity_domain = []
        channel_id = self.channel_id
        duplicity_config = self.env['multi.channel.sale.config'].get_values()
        domain = [
            ('channel_id', '=', channel_id.id),
        ]
        if res_model in [
                'channel.product.mappings', 'channel.template.mappings'
        ] and duplicity_config.get('avoid_duplicity'):
            avoid_duplicity_using = duplicity_config.get(
                'avoid_duplicity_using')
            barcode = self.barcode
            default_code = self.default_code
            domain += ['|', (store_field, '=', self.store_id)]
            if default_code and barcode:
                domain += [
                    '|', ('barcode', '=', barcode),
                    ('default_code', '=', default_code)
                ]
            elif default_code:
                domain += [('default_code', '=', default_code)]
            else:
                domain += [('barcode', '=', barcode)]
        else:
            domain += [(store_field, '=', self.store_id)]
        mapping_ids = self.env[res_model].search(domain).ids
        return {
            'name': ('Mapping'),
            'type': 'ir.actions.act_window',
            'view_type': 'form',
            'view_mode': 'tree,form',
            'res_model': res_model,
            'view_id': False,
            'domain': [('id', 'in', mapping_ids)],
            'target': 'current',
        }

    @api.multi
    def set_feed_state(self, state='done'):
        self.state = state
        return True

    @api.multi
    def get_feed_result(self, feed_type):
        message = ""
        tot = len(self)
        if tot == 1:
            if self.state == 'done':
                message += "{_type} feed sucessfully evaluated .".format(
                    _type=feed_type)
            else:
                message += "{_type} feed failed to evaluate .".format(
                    _type=feed_type)
        else:
            done_feeds = self.filtered(lambda feed: feed.state == 'done')
            error_feed = tot - len(done_feeds)
            if not error_feed:
                message += "All ({done}) {_type} feed sucessfully evaluated .".format(
                    done=len(done_feeds), _type=feed_type)
            else:
                message += "<br/>{error} {_type} feed failed to evaluate".format(
                    error=error_feed, _type=feed_type)
                if len(done_feeds):
                    message += "<br/>{done} {_type} feed sucessfully evaluated".format(
                        done=len(done_feeds), _type=feed_type)
        return message

    @api.model
    def get_channel_domain(self):
        return [('channel_id', '=', self.channel_id.id)]

    @api.model
    def get_categ_id(self, store_categ_id, channel_id):

        message = ''
        categ_id = None
        match = channel_id.match_category_mappings(store_categ_id)
        if match:
            categ_id = match.flectra_category_id
        else:
            feed = channel_id.match_category_feeds(store_categ_id)
            if feed:
                res = feed.import_category(channel_id)
                message += res.get('message', '')
                mapping_id = res.get('update_id') or res.get('create_id')
                if mapping_id:
                    categ_id = mapping_id.flectra_category_id
            else:
                message += '<br/>Category Feed Error: No mapping as well category feed found for %s .' % (
                    store_categ_id)
        return dict(categ_id=categ_id, message=message)

    @api.model
    def get_extra_categ_ids(self, store_categ_ids, channel_id):
        message = ''
        categ_ids = []
        for store_categ_id in store_categ_ids.strip(',').split(','):
            res = self.get_categ_id(store_categ_id, channel_id)
            message += res.get('message', '')
            categ_id = res.get('categ_id')
            if categ_id:
                categ_ids += [categ_id]
        return dict(categ_ids=categ_ids, message=message)

    @api.model
    def get_order_partner_id(self, store_partner_id, channel_id):
        partner_obj = self.env['res.partner']
        message = ''
        partner_id = None
        partner_invoice_id = None
        partner_shipping_id = None
        context = dict(self._context)
        context['no_mapping'] = self.customer_is_guest
        partner_id = self.with_context(context).create_partner_contact_id(
            partner_id, channel_id, store_partner_id)
        partner_invoice_id = self.with_context(
            context).create_partner_invoice_id(partner_id, channel_id,
                                               self.invoice_partner_id)
        if self.same_shipping_billing:
            partner_shipping_id = partner_invoice_id
        else:
            partner_shipping_id = self.with_context(
                context).create_partner_shipping_id(partner_id, channel_id,
                                                    partner_shipping_id)
        return dict(partner_id=partner_id,
                    partner_shipping_id=partner_shipping_id,
                    partner_invoice_id=partner_invoice_id,
                    message=message)

    @api.model
    def get_partner_id(self,
                       store_partner_id,
                       _type='contact',
                       channel_id=None):
        partner_obj = self.env['res.partner']
        message = ''
        partner_id = None
        match = channel_id.match_partner_mappings(store_partner_id, _type)
        if match:
            partner_id = match.flectra_partner
        else:
            feed = channel_id.match_partner_feeds(store_partner_id, _type)
            if feed:
                res = feed.import_partner(channel_id)
                message += res.get('message', '')
                mapping_id = res.get('update_id') or res.get('create_id')
                if mapping_id:
                    partner_id = mapping_id.flectra_partner
            else:
                message += '<br/>Partner Feed Error: No mapping as well partner feed found for %s.' % (
                    store_partner_id)

        return dict(partner_id=partner_id, message=message)

    @api.model
    def get_product_id(self,
                       store_product_id,
                       line_variant_ids,
                       channel_id,
                       default_code=None,
                       barcode=None):
        message = ''
        domain = []
        if default_code:
            domain += [
                ('default_code', '=', default_code),
            ]
        if barcode:
            domain += [
                ('barcode', '=', barcode),
            ]
        product_id = None
        match = channel_id.match_product_mappings(store_product_id,
                                                  line_variant_ids,
                                                  domain=domain)
        if match:
            product_id = match.product_name
        else:
            feed = channel_id.match_product_feeds(store_product_id,
                                                  domain=domain)
            product_variant_feeds = channel_id.match_product_variant_feeds(
                store_product_id, domain=domain)
            if feed:
                res = feed.import_product(channel_id)
                message += res.get('message', '')
                mapping_id = res.get('update_id') or res.get('create_id')

                match = channel_id.match_product_mappings(store_product_id,
                                                          line_variant_ids,
                                                          domain=domain)
                if match:
                    product_id = match.product_name
            elif product_variant_feeds and product_variant_feeds.feed_templ_id:
                res = product_variant_feeds.feed_templ_id.import_product(
                    channel_id)
                message += res.get('message', '')
                match = channel_id.match_product_mappings(
                    line_variant_ids=store_product_id)
                if match:
                    product_id = match.product_name
            else:
                message += '<br/>Product Feed Error: For product id (%s) sku (%s) no mapping as well feed found.' % (
                    store_product_id, default_code)
        return dict(product_id=product_id, message=message)

    @api.model
    def get_carrier_id(self, carrier_id, service_id=None, channel_id=None):
        message = ''
        res_id = None
        shipping_service_name = service_id and service_id or carrier_id
        match = channel_id.match_carrier_mappings(shipping_service_name)
        if match:
            res_id = match.flectra_shipping_carrier
        else:
            res_id = channel_id.create_carrier_mapping(carrier_id, service_id)
        return dict(carrier_id=res_id, message=message)

    def get_partner_invoice_vals(self, partner_id, channel_id):
        name = self.invoice_name
        if self.invoice_last_name:
            name = '%s %s' % (name, self.invoice_last_name)
        vals = dict(
            type='invoice',
            name=self.invoice_name,
            street=self.invoice_street,
            street2=self.invoice_street2,
            city=self.invoice_city,
            zip=self.invoice_zip,
            email=self.invoice_email,
            phone=self.invoice_phone,
            mobile=self.invoice_mobile,
            parent_id=partner_id.id,
            customer=False,
        )
        country_id = self.invoice_country_id and channel_id.get_country_id(
            self.invoice_country_id)
        if country_id:
            vals['country_id'] = country_id.id
        state_id = (self.invoice_state_id or self.invoice_state_name
                    ) and country_id and channel_id.get_state_id(
                        self.invoice_state_id, country_id,
                        self.invoice_state_name)
        if state_id:
            vals['state_id'] = state_id.id
        return vals

    @api.model
    def create_partner_invoice_id(self,
                                  partner_id,
                                  channel_id,
                                  invoice_partner_id=None):
        partner_obj = self.env['res.partner']
        vals = self.get_partner_invoice_vals(partner_id, channel_id)
        match = None
        if invoice_partner_id:
            match = channel_id.match_partner_mappings(invoice_partner_id,
                                                      'invoice')
        if match:
            match.flectra_partner.write(vals)
            erp_id = match.flectra_partner
        else:
            erp_id = partner_obj.create(vals)
            if (not self._context.get('no_mapping') and invoice_partner_id):
                channel_id.create_partner_mapping(erp_id, invoice_partner_id,
                                                  'invoice')
        return erp_id

    def get_partner_shipping_vals(self, partner_id, channel_id):

        name = self.shipping_name
        if self.shipping_last_name:
            name = '%s %s' % (name, self.shipping_last_name)
        vals = dict(
            type='delivery',
            name=self.shipping_name,
            street=self.shipping_street,
            street2=self.shipping_street2,
            city=self.shipping_city,
            zip=self.shipping_zip,
            email=self.shipping_email,
            phone=self.shipping_phone,
            mobile=self.shipping_mobile,
            parent_id=partner_id.id,
            customer=False,
        )
        country_id = self.shipping_country_id and channel_id.get_country_id(
            self.shipping_country_id)
        if country_id:
            vals['country_id'] = country_id.id
        state_id = (self.shipping_state_id or self.shipping_state_name
                    ) and country_id and channel_id.get_state_id(
                        self.shipping_state_id, country_id,
                        self.shipping_state_name)
        if state_id:
            vals['state_id'] = state_id.id
        return vals

    @api.model
    def create_partner_shipping_id(self,
                                   partner_id,
                                   channel_id,
                                   shipping_partner_id=None):
        partner_obj = self.env['res.partner']
        match = None
        vals = self.get_partner_shipping_vals(partner_id, channel_id)
        if shipping_partner_id:
            match = channel_id.match_partner_mappings(shipping_partner_id,
                                                      'delivery')
        if match:
            match.flectra_partner.write(vals)
            erp_id = match.flectra_partner
        else:
            erp_id = partner_obj.create(vals)
            if (not self._context.get('no_mapping') and shipping_partner_id):
                channel_id.create_partner_mapping(erp_id, shipping_partner_id,
                                                  'delivery')
        return erp_id

    def get_partner_contact_vals(self, partner_id, channel_id):
        _type = 'contact'
        name = self.customer_name
        if self.customer_last_name:
            name = '%s %s' % (name, self.customer_last_name)
        vals = dict(
            type=_type,
            customer=1,
            name=self.customer_name,
            email=self.customer_email,
            phone=self.customer_phone,
            mobile=self.customer_mobile,
        )
        return vals

    @api.model
    def create_partner_contact_id(self,
                                  partner_id,
                                  channel_id,
                                  store_partner_id=None):
        partner_obj = self.env['res.partner']
        vals = self.get_partner_contact_vals(partner_id, channel_id)
        match = None
        if store_partner_id:
            match = channel_id.match_partner_mappings(store_partner_id,
                                                      'contact')
        if match:
            match.flectra_partner.write(vals)
            erp_id = match.flectra_partner
        else:
            erp_id = partner_obj.create(vals)
            if (not self._context.get('no_mapping') and store_partner_id):
                channel_id.create_partner_mapping(erp_id, store_partner_id,
                                                  'contact')
        return erp_id
コード例 #10
0
ファイル: pos_restaurant.py プロジェクト: devexpat/kouterp
class RestaurantTable(models.Model):

    _name = 'restaurant.table'

    name = fields.Char('Table Name',
                       required=True,
                       help='An internal identification of a table')
    floor_id = fields.Many2one('restaurant.floor', string='Floor')
    shape = fields.Selection([('square', 'Square'), ('round', 'Round')],
                             string='Shape',
                             required=True,
                             default='square')
    position_h = fields.Float(
        'Horizontal Position',
        default=10,
        help=
        "The table's horizontal position from the left side to the table's center, in pixels"
    )
    position_v = fields.Float(
        'Vertical Position',
        default=10,
        help=
        "The table's vertical position from the top to the table's center, in pixels"
    )
    width = fields.Float('Width',
                         default=50,
                         help="The table's width in pixels")
    height = fields.Float('Height',
                          default=50,
                          help="The table's height in pixels")
    seats = fields.Integer(
        'Seats',
        default=1,
        help="The default number of customer served at this table.")
    color = fields.Char(
        'Color',
        help=
        "The table's color, expressed as a valid 'background' CSS property value"
    )
    active = fields.Boolean(
        'Active',
        default=True,
        help=
        'If false, the table is deactivated and will not be available in the point of sale'
    )

    @api.model
    def create_from_ui(self, table):
        """ create or modify a table from the point of sale UI.
            table contains the table's fields. If it contains an
            id, it will modify the existing table. It then
            returns the id of the table.
        """
        if table.get('floor_id'):
            table['floor_id'] = table['floor_id'][0]

        table_id = table.pop('id', False)
        if table_id:
            self.browse(table_id).write(table)
        else:
            table_id = self.create(table).id
        return table_id
コード例 #11
0
class ResConfigSettings(models.TransientModel):
    _inherit = 'res.config.settings'

    default_template_id = fields.Many2one('sale.quote.template',
                                          default_model='sale.order',
                                          string='Default Template')
コード例 #12
0
class Goal(models.Model):
    """Goal instance for a user

    An individual goal for a user on a specified time period"""

    _name = 'gamification.goal'
    _description = 'Gamification goal instance'
    _order = 'start_date desc, end_date desc, definition_id, id'

    definition_id = fields.Many2one('gamification.goal.definition',
                                    string="Goal Definition",
                                    required=True,
                                    ondelete="cascade")
    user_id = fields.Many2one('res.users',
                              string="User",
                              required=True,
                              auto_join=True,
                              ondelete="cascade")
    line_id = fields.Many2one('gamification.challenge.line',
                              string="Challenge Line",
                              ondelete="cascade")
    challenge_id = fields.Many2one(
        related='line_id.challenge_id',
        store=True,
        readonly=True,
        help="Challenge that generated the goal, assign challenge to users "
        "to generate goals with a value in this field.")
    start_date = fields.Date("Start Date", default=fields.Date.today)
    end_date = fields.Date("End Date")  # no start and end = always active
    target_goal = fields.Float('To Reach',
                               required=True,
                               track_visibility='always')
    # no goal = global index
    current = fields.Float("Current Value",
                           required=True,
                           default=0,
                           track_visibility='always')
    completeness = fields.Float("Completeness", compute='_get_completion')
    state = fields.Selection([
        ('draft', "Draft"),
        ('inprogress', "In progress"),
        ('reached', "Reached"),
        ('failed', "Failed"),
        ('canceled', "Canceled"),
    ],
                             default='draft',
                             string='State',
                             required=True,
                             track_visibility='always')
    to_update = fields.Boolean('To update')
    closed = fields.Boolean('Closed goal',
                            help="These goals will not be recomputed.")

    computation_mode = fields.Selection(
        related='definition_id.computation_mode')
    remind_update_delay = fields.Integer(
        "Remind delay",
        help="The number of days after which the user "
        "assigned to a manual goal will be reminded. "
        "Never reminded if no value is specified.")
    last_update = fields.Date(
        "Last Update",
        help="In case of manual goal, reminders are sent if the goal as not "
        "been updated for a while (defined in challenge). Ignored in "
        "case of non-manual goal or goal not linked to a challenge.")

    definition_description = fields.Text("Definition Description",
                                         related='definition_id.description',
                                         readonly=True)
    definition_condition = fields.Selection("Definition Condition",
                                            related='definition_id.condition',
                                            readonly=True)
    definition_suffix = fields.Char("Suffix",
                                    related='definition_id.full_suffix',
                                    readonly=True)
    definition_display = fields.Selection("Display Mode",
                                          related='definition_id.display_mode',
                                          readonly=True)

    @api.depends('current', 'target_goal', 'definition_id.condition')
    def _get_completion(self):
        """Return the percentage of completeness of the goal, between 0 and 100"""
        for goal in self:
            if goal.definition_condition == 'higher':
                if goal.current >= goal.target_goal:
                    goal.completeness = 100.0
                else:
                    goal.completeness = round(
                        100.0 * goal.current / goal.target_goal, 2)
            elif goal.current < goal.target_goal:
                # a goal 'lower than' has only two values possible: 0 or 100%
                goal.completeness = 100.0
            else:
                goal.completeness = 0.0

    def _check_remind_delay(self):
        """Verify if a goal has not been updated for some time and send a
        reminder message of needed.

        :return: data to write on the goal object
        """
        if not (self.remind_update_delay and self.last_update):
            return {}

        delta_max = timedelta(days=self.remind_update_delay)
        last_update = fields.Date.from_string(self.last_update)
        if date.today() - last_update < delta_max:
            return {}

        # generate a reminder report
        template = self.env.ref('gamification.email_template_goal_reminder')\
                           .get_email_template(self.id)
        body_html = self.env['mail.template'].with_context(template._context)\
            .render_template(template.body_html, 'gamification.goal', self.id)
        self.env['mail.thread'].message_post(
            body=body_html,
            partner_ids=[self.user_id.partner_id.id],
            subtype='mail.mt_comment')

        return {'to_update': True}

    def _get_write_values(self, new_value):
        """Generate values to write after recomputation of a goal score"""
        if new_value == self.current:
            # avoid useless write if the new value is the same as the old one
            return {}

        result = {'current': new_value}
        if (self.definition_id.condition == 'higher' and new_value >= self.target_goal) \
          or (self.definition_id.condition == 'lower' and new_value <= self.target_goal):
            # success, do no set closed as can still change
            result['state'] = 'reached'

        elif self.end_date and fields.Date.today() > self.end_date:
            # check goal failure
            result['state'] = 'failed'
            result['closed'] = True

        return {self: result}

    @api.multi
    def update_goal(self):
        """Update the goals to recomputes values and change of states

        If a manual goal is not updated for enough time, the user will be
        reminded to do so (done only once, in 'inprogress' state).
        If a goal reaches the target value, the status is set to reached
        If the end date is passed (at least +1 day, time not considered) without
        the target value being reached, the goal is set as failed."""
        goals_by_definition = {}
        for goal in self:
            goals_by_definition.setdefault(goal.definition_id, []).append(goal)

        for definition, goals in goals_by_definition.items():
            goals_to_write = {}
            if definition.computation_mode == 'manually':
                for goal in goals:
                    goals_to_write[goal] = goal._check_remind_delay()
            elif definition.computation_mode == 'python':
                # TODO batch execution
                for goal in goals:
                    # execute the chosen method
                    cxt = {
                        'object': goal,
                        'env': self.env,
                        'date': date,
                        'datetime': datetime,
                        'timedelta': timedelta,
                        'time': time,
                    }
                    code = definition.compute_code.strip()
                    safe_eval(code, cxt, mode="exec", nocopy=True)
                    # the result of the evaluated codeis put in the 'result' local variable, propagated to the context
                    result = cxt.get('result')
                    if result is not None and isinstance(
                            result, (float, pycompat.integer_types)):
                        goals_to_write.update(goal._get_write_values(result))
                    else:
                        _logger.error(
                            "Invalid return content '%r' from the evaluation "
                            "of code for definition %s, expected a number",
                            result, definition.name)

            else:  # count or sum
                Obj = self.env[definition.model_id.model]

                field_date_name = definition.field_date_id.name
                if definition.computation_mode == 'count' and definition.batch_mode:
                    # batch mode, trying to do as much as possible in one request
                    general_domain = safe_eval(definition.domain)
                    field_name = definition.batch_distinctive_field.name
                    subqueries = {}
                    for goal in goals:
                        start_date = field_date_name and goal.start_date or False
                        end_date = field_date_name and goal.end_date or False
                        subqueries.setdefault(
                            (start_date, end_date), {}).update({
                                goal.id:
                                safe_eval(definition.batch_user_expression,
                                          {'user': goal.user_id})
                            })

                    # the global query should be split by time periods (especially for recurrent goals)
                    for (start_date,
                         end_date), query_goals in subqueries.items():
                        subquery_domain = list(general_domain)
                        subquery_domain.append(
                            (field_name, 'in',
                             list(set(query_goals.values()))))
                        if start_date:
                            subquery_domain.append(
                                (field_date_name, '>=', start_date))
                        if end_date:
                            subquery_domain.append(
                                (field_date_name, '<=', end_date))

                        if field_name == 'id':
                            # grouping on id does not work and is similar to search anyway
                            users = Obj.search(subquery_domain)
                            user_values = [{
                                'id': user.id,
                                'id_count': 1
                            } for user in users]
                        else:
                            user_values = Obj.read_group(subquery_domain,
                                                         fields=[field_name],
                                                         groupby=[field_name])
                        # user_values has format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...]
                        for goal in [g for g in goals if g.id in query_goals]:
                            for user_value in user_values:
                                queried_value = field_name in user_value and user_value[
                                    field_name] or False
                                if isinstance(queried_value, tuple) and len(
                                        queried_value) == 2 and isinstance(
                                            queried_value[0],
                                            pycompat.integer_types):
                                    queried_value = queried_value[0]
                                if queried_value == query_goals[goal.id]:
                                    new_value = user_value.get(
                                        field_name + '_count', goal.current)
                                    goals_to_write.update(
                                        goal._get_write_values(new_value))

                else:
                    for goal in goals:
                        # eval the domain with user replaced by goal user object
                        domain = safe_eval(definition.domain,
                                           {'user': goal.user_id})

                        # add temporal clause(s) to the domain if fields are filled on the goal
                        if goal.start_date and field_date_name:
                            domain.append(
                                (field_date_name, '>=', goal.start_date))
                        if goal.end_date and field_date_name:
                            domain.append(
                                (field_date_name, '<=', goal.end_date))

                        if definition.computation_mode == 'sum':
                            field_name = definition.field_id.name
                            # TODO for master: group on user field in batch mode
                            res = Obj.read_group(domain, [field_name], [])
                            new_value = res and res[0][field_name] or 0.0

                        else:  # computation mode = count
                            new_value = Obj.search_count(domain)

                        goals_to_write.update(
                            goal._get_write_values(new_value))

            for goal, values in goals_to_write.items():
                if not values:
                    continue
                goal.write(values)
            if self.env.context.get('commit_gamification'):
                self.env.cr.commit()
        return True

    @api.multi
    def action_start(self):
        """Mark a goal as started.

        This should only be used when creating goals manually (in draft state)"""
        self.write({'state': 'inprogress'})
        return self.update_goal()

    @api.multi
    def action_reach(self):
        """Mark a goal as reached.

        If the target goal condition is not met, the state will be reset to In
        Progress at the next goal update until the end date."""
        return self.write({'state': 'reached'})

    @api.multi
    def action_fail(self):
        """Set the state of the goal to failed.

        A failed goal will be ignored in future checks."""
        return self.write({'state': 'failed'})

    @api.multi
    def action_cancel(self):
        """Reset the completion after setting a goal as reached or failed.

        This is only the current state, if the date and/or target criteria
        match the conditions for a change of state, this will be applied at the
        next goal update."""
        return self.write({'state': 'inprogress'})

    @api.model
    def create(self, vals):
        return super(Goal, self.with_context(no_remind_goal=True)).create(vals)

    @api.multi
    def write(self, vals):
        """Overwrite the write method to update the last_update field to today

        If the current value is changed and the report frequency is set to On
        change, a report is generated
        """
        vals['last_update'] = fields.Date.today()
        result = super(Goal, self).write(vals)
        for goal in self:
            if goal.state != "draft" and ('definition_id' in vals
                                          or 'user_id' in vals):
                # avoid drag&drop in kanban view
                raise exceptions.UserError(
                    _('Can not modify the configuration of a started goal'))

            if vals.get(
                    'current') and 'no_remind_goal' not in self.env.context:
                if goal.challenge_id.report_message_frequency == 'onchange':
                    goal.challenge_id.sudo().report_progress(
                        users=goal.user_id)
        return result

    @api.multi
    def get_action(self):
        """Get the ir.action related to update the goal

        In case of a manual goal, should return a wizard to update the value
        :return: action description in a dictionary
        """
        if self.definition_id.action_id:
            # open a the action linked to the goal
            action = self.definition_id.action_id.read()[0]

            if self.definition_id.res_id_field:
                current_user = self.env.user.sudo(self.env.user)
                action['res_id'] = safe_eval(self.definition_id.res_id_field,
                                             {'user': current_user})

                # if one element to display, should see it in form mode if possible
                action['views'] = [(view_id, mode)
                                   for (view_id, mode) in action['views']
                                   if mode == 'form'] or action['views']
            return action

        if self.computation_mode == 'manually':
            # open a wizard window to update the value manually
            action = {
                'name': _("Update %s") % self.definition_id.name,
                'id': self.id,
                'type': 'ir.actions.act_window',
                'views': [[False, 'form']],
                'target': 'new',
                'context': {
                    'default_goal_id': self.id,
                    'default_current': self.current
                },
                'res_model': 'gamification.goal.wizard'
            }
            return action

        return False
コード例 #13
0
class GoalDefinition(models.Model):
    """Goal definition

    A goal definition contains the way to evaluate an objective
    Each module wanting to be able to set goals to the users needs to create
    a new gamification_goal_definition
    """
    _name = 'gamification.goal.definition'
    _description = 'Gamification goal definition'

    name = fields.Char("Goal Definition", required=True, translate=True)
    description = fields.Text("Goal Description")
    monetary = fields.Boolean(
        "Monetary Value",
        default=False,
        help="The target and current value are defined in the company currency."
    )
    suffix = fields.Char("Suffix",
                         help="The unit of the target and current values",
                         translate=True)
    full_suffix = fields.Char("Full Suffix",
                              compute='_compute_full_suffix',
                              help="The currency and suffix field")
    computation_mode = fields.Selection(
        [
            ('manually', "Recorded manually"),
            ('count', "Automatic: number of records"),
            ('sum', "Automatic: sum on a field"),
            ('python', "Automatic: execute a specific Python code"),
        ],
        default='manually',
        string="Computation Mode",
        required=True,
        help=
        "Defined how will be computed the goals. The result of the operation will be stored in the field 'Current'."
    )
    display_mode = fields.Selection([
        ('progress', "Progressive (using numerical values)"),
        ('boolean', "Exclusive (done or not-done)"),
    ],
                                    default='progress',
                                    string="Displayed as",
                                    required=True)
    model_id = fields.Many2one(
        'ir.model',
        string='Model',
        help='The model object for the field to evaluate')
    field_id = fields.Many2one(
        'ir.model.fields',
        string='Field to Sum',
        help='The field containing the value to evaluate')
    field_date_id = fields.Many2one(
        'ir.model.fields',
        string='Date Field',
        help='The date to use for the time period evaluated')
    domain = fields.Char(
        "Filter Domain",
        required=True,
        default="[]",
        help="Domain for filtering records. General rule, not user depending,"
        " e.g. [('state', '=', 'done')]. The expression can contain"
        " reference to 'user' which is a browse record of the current"
        " user if not in batch mode.")

    batch_mode = fields.Boolean(
        "Batch Mode",
        help="Evaluate the expression in batch instead of once for each user")
    batch_distinctive_field = fields.Many2one(
        'ir.model.fields',
        string="Distinctive field for batch user",
        help=
        "In batch mode, this indicates which field distinct one user form the other, e.g. user_id, partner_id..."
    )
    batch_user_expression = fields.Char(
        "Evaluated expression for batch mode",
        help=
        "The value to compare with the distinctive field. The expression can contain reference to 'user' which is a browse record of the current user, e.g. user.id, user.partner_id.id..."
    )
    compute_code = fields.Text(
        "Python Code",
        help=
        "Python code to be executed for each user. 'result' should contains the new current value. Evaluated user can be access through object.user_id."
    )
    condition = fields.Selection(
        [('higher', "The higher the better"),
         ('lower', "The lower the better")],
        default='higher',
        required=True,
        string="Goal Performance",
        help=
        "A goal is considered as completed when the current value is compared to the value to reach"
    )
    action_id = fields.Many2one(
        'ir.actions.act_window',
        string="Action",
        help="The action that will be called to update the goal value.")
    res_id_field = fields.Char(
        "ID Field of user",
        help=
        "The field name on the user profile (res.users) containing the value for res_id for action."
    )

    @api.depends('suffix', 'monetary')  # also depends of user...
    def _compute_full_suffix(self):
        for goal in self:
            items = []

            if goal.monetary:
                items.append(self.env.user.company_id.currency_id.symbol
                             or u'¤')
            if goal.suffix:
                items.append(goal.suffix)

            goal.full_suffix = u' '.join(items)

    def _check_domain_validity(self):
        # take admin as should always be present
        for definition in self:
            if definition.computation_mode not in ('count', 'sum'):
                continue

            Obj = self.env[definition.model_id.model]
            try:
                domain = safe_eval(definition.domain,
                                   {'user': self.env.user.sudo(self.env.user)})
                # dummy search to make sure the domain is valid
                Obj.search_count(domain)
            except (ValueError, SyntaxError) as e:
                msg = e
                if isinstance(e, SyntaxError):
                    msg = (e.msg + '\n' + e.text)
                raise exceptions.UserError(
                    _("The domain for the definition %s seems incorrect, please check it.\n\n%s"
                      ) % (definition.name, msg))
        return True

    def _check_model_validity(self):
        """ make sure the selected field and model are usable"""
        for definition in self:
            try:
                if not (definition.model_id and definition.field_id):
                    continue

                Model = self.env[definition.model_id.model]
                field = Model._fields.get(definition.field_id.name)
                if not (field and field.store):
                    raise exceptions.UserError(
                        _("The model configuration for the definition %s seems incorrect, please check it.\n\n%s not stored"
                          ) % (definition.name, definition.field_id.name))
            except KeyError as e:
                raise exceptions.UserError(
                    _("The model configuration for the definition %s seems incorrect, please check it.\n\n%s not found"
                      ) % (definition.name, e))

    @api.model
    def create(self, vals):
        definition = super(GoalDefinition, self).create(vals)
        if definition.computation_mode in ('count', 'sum'):
            definition._check_domain_validity()
        if vals.get('field_id'):
            definition._check_model_validity()
        return definition

    @api.multi
    def write(self, vals):
        res = super(GoalDefinition, self).write(vals)
        if vals.get('computation_mode',
                    'count') in ('count', 'sum') and (vals.get('domain')
                                                      or vals.get('model_id')):
            self._check_domain_validity()
        if vals.get('field_id') or vals.get('model_id') or vals.get(
                'batch_mode'):
            self._check_model_validity()
        return res

    @api.onchange('model_id')
    def _change_model_id(self):
        """Force domain for the `field_id` and `field_date_id` fields"""
        if not self.model_id:
            return {
                'domain': {
                    'field_id': expression.FALSE_DOMAIN,
                    'field_date_id': expression.FALSE_DOMAIN
                }
            }
        model_fields_domain = [('store', '=', True), '|',
                               ('model_id', '=', self.model_id.id),
                               ('model_id', 'in',
                                self.model_id.inherited_model_ids.ids)]
        model_date_fields_domain = expression.AND([[
            ('ttype', 'in', ('date', 'datetime'))
        ], model_fields_domain])
        return {
            'domain': {
                'field_id': model_fields_domain,
                'field_date_id': model_date_fields_domain
            }
        }
コード例 #14
0
class Directory(dms_base.DMSModel):
    _name = 'muk_dms.directory'
    _description = "MuK Documents Directory"

    _inherit = 'muk_dms.access'

    _parent_store = True
    _parent_name = "parent_directory"
    _parent_order = 'parent_left'
    _order = 'parent_left'

    #----------------------------------------------------------
    # Database
    #----------------------------------------------------------

    name = fields.Char(string="Name", required=True)

    is_root_directory = fields.Boolean(
        string="Root Directory",
        default=False,
        help=
        "Indicates if the directory is a root directory. A root directory has a settings object, "
        +
        "while a directory with a set parent inherits the settings form its parent."
    )

    settings = fields.Many2one('muk_dms.settings',
                               string="Settings",
                               store=True,
                               auto_join=True,
                               ondelete='restrict',
                               compute='_compute_settings')

    parent_directory = fields.Many2one('muk_dms.directory',
                                       string="Parent Directory",
                                       ondelete='restrict',
                                       auto_join=True,
                                       index=True)

    child_directories = fields.One2many('muk_dms.directory',
                                        'parent_directory',
                                        copy=False,
                                        string="Subdirectories")

    parent_left = fields.Integer(string='Left Parent', index=True)

    parent_right = fields.Integer(string='Right Parent', index=True)

    files = fields.One2many('muk_dms.file',
                            'directory',
                            copy=False,
                            string="Files")

    count_directories = fields.Integer(compute='_compute_count_directories',
                                       string="Subdirectories")

    count_files = fields.Integer(compute='_compute_count_files',
                                 string="Files")

    size = fields.Integer(compute='_compute_size', string="Size", store=True)

    custom_thumbnail = fields.Binary(string="Custom Thumbnail")

    thumbnail = fields.Binary(compute='_compute_thumbnail', string="Thumbnail")

    path = fields.Char(string="Path", store=True, compute='_compute_path')

    relational_path = fields.Text(string="Path",
                                  store=True,
                                  readonly=True,
                                  compute='_compute_relational_path')

    #----------------------------------------------------------
    # Functions
    #----------------------------------------------------------

    def lock_tree(self,
                  user=None,
                  refresh=False,
                  operation=None,
                  lock_self=True):
        if lock_self:
            self.lock(operation=operation)
        for record in self:
            record.child_directories.lock_tree(operation=operation)
            record.files.lock(operation=operation)

    def unlock_tree(self, refresh=False):
        self.unlock()
        for record in self:
            record.child_directories.unlock_tree()
            record.files.unlock()

    def notify_change(self, values, refresh=False, operation=None):
        super(Directory, self).notify_change(values, refresh)
        for child in self.child_directories:
            child.notify_change(values, refresh)
        for index, file in enumerate(self.files):
            if index == len(self.files) - 1:
                file.notify_change(values, refresh)
            else:
                file.notify_change(values, refresh)

    def trigger_computation_up(self, fields, operation=None):
        parent_directory = self.parent_directory
        if parent_directory:
            parent_directory.trigger_computation(fields, False)

    def trigger_computation_down(self, fields, operation=None):
        if self.settings.system_locks:
            self.lock_tree(operation=operation, lock_self=False)
        for child in self.child_directories:
            child.with_context(operation=operation,
                               is_subnode=True).trigger_computation(
                                   fields, False, operation)
        for index, file in enumerate(self.files):
            if index == len(self.files) - 1:
                if self.env.context.get('is_subnode'):
                    file.with_context(operation=operation).trigger_computation(
                        fields, False, operation)
                else:
                    file.with_context(operation=operation).trigger_computation(
                        fields, True, operation)
            else:
                file.with_context(operation=operation).trigger_computation(
                    fields, False, operation)
        self.unlock_tree()

    def trigger_computation(self, fields, refresh=True, operation=None):
        super(Directory, self).trigger_computation(fields, refresh, operation)
        values = {}
        if "settings" in fields:
            if not self.is_root_directory:
                values.update(
                    self.with_context(operation=operation)._compute_settings(
                        write=False))
        if "path" in fields:
            values.update(
                self.with_context(operation=operation)._compute_path(
                    write=False))
            values.update(
                self.with_context(
                    operation=operation)._compute_relational_path(write=False))
        if "size" in fields:
            values.update(
                self.with_context(operation=operation)._compute_size(
                    write=False))
        if values:
            self.write(values)
            if "settings" in fields or "path" in fields:
                self.trigger_computation_down(fields, operation)
            if "size" in fields:
                self.trigger_computation_up(fields, operation)

    #----------------------------------------------------------
    # Read, View
    #----------------------------------------------------------

    def _compute_settings(self, write=True):
        if write:
            for record in self:
                if not record.is_root_directory:
                    record.settings = record.parent_directory.settings
        else:
            self.ensure_one()
            return {'settings': self.parent_directory.settings.id}

    def _compute_path(self, write=True):
        def get_path(record):
            if record.is_root_directory:
                return "/%s/" % record.name
            else:
                return "%s%s/" % (record.parent_directory.path, record.name)

        if write:
            for record in self:
                record.path = get_path(record)
        else:
            self.ensure_one()
            return {'path': get_path(self)}

    def _compute_relational_path(self, write=True):
        def get_relational_path(record):
            if record.is_root_directory:
                return json.dumps([{
                    'model': record._name,
                    'id': record.id,
                    'name': record.name
                }])
            else:
                path = json.loads(record.parent_directory.relational_path)
                path.append({
                    'model': record._name,
                    'id': record.id,
                    'name': record.name
                })
                return json.dumps(path)

        if write:
            for record in self:
                record.relational_path = get_relational_path(record)
        else:
            self.ensure_one()
            return {'relational_path': get_relational_path(self)}

    def _compute_size(self, write=True):
        def get_size(record):
            size = 0
            for directory in record.child_directories:
                size += directory.size
            for file in record.files:
                size += file.size
            return size

        if write:
            for record in self:
                record.size = get_size(record)
        else:
            self.ensure_one()
            return {'size': get_size(self)}

    @api.depends('child_directories')
    def _compute_count_directories(self):
        for record in self:
            record.count_directories = len(record.child_directories)

    @api.depends('files')
    def _compute_count_files(self):
        for record in self:
            record.count_files = len(record.files)

    @api.depends('custom_thumbnail')
    def _compute_thumbnail(self):
        for record in self:
            if record.custom_thumbnail:
                record.thumbnail = record.with_context({}).custom_thumbnail
            else:
                with open(os.path.join(_img_path, "folder.png"),
                          "rb") as image_file:
                    record.thumbnail = base64.b64encode(image_file.read())

    #----------------------------------------------------------
    # Create, Update, Delete
    #----------------------------------------------------------

    @api.onchange('is_root_directory')
    def _onchange_directory_type(self):
        if self.is_root_directory:
            self.parent_directory = None
        else:
            self.settings = None

    def _before_create(self, vals):
        vals = super(Directory, self)._before_create(vals)
        is_root_directory = vals[
            "is_root_directory"] if "is_root_directory" in vals else False
        if is_root_directory and not "settings" in vals:
            raise ValidationError(
                "A root directory has to have a settings object.")
        if not is_root_directory and not "parent_directory" in vals:
            raise ValidationError(
                "A directory has to have a parent directory.")
        return vals

    @api.constrains('settings', 'parent_directory')
    def _check_settings(self):
        if self.is_root_directory and not self.settings:
            raise ValidationError(
                "A root directory has to have a settings object.")
        if not self.is_root_directory and not self.parent_directory:
            raise ValidationError(
                "A directory has to have a parent directory.")

    @api.constrains('name')
    def _check_name(self):
        if not self.check_name(self.name):
            raise ValidationError("The directory name is invalid.")
        if self.is_root_directory:
            childs = self.sudo().settings.root_directories.mapped(
                lambda rec: [rec.id, rec.name])
        else:
            childs = self.sudo().parent_directory.child_directories.mapped(
                lambda rec: [rec.id, rec.name])
        duplicates = [
            rec for rec in childs if rec[1] == self.name and rec[0] != self.id
        ]
        if duplicates:
            raise ValidationError(
                "A directory with the same name already exists.")

    def _after_create(self, vals):
        record = super(Directory, self)._after_create(vals)
        record._check_recomputation(vals)
        return record

    def _after_write_record(self, vals, operation):
        vals = super(Directory, self)._after_write_record(vals, operation)
        self._check_recomputation(vals, operation)
        return vals

    def _check_recomputation(self, values, operation=None):
        fields = []
        if 'name' in values:
            fields.extend(['path'])
        if self.is_root_directory and 'settings' in values:
            fields.extend(['settings'])
        if not self.is_root_directory and 'parent_directory' in values:
            fields.extend(['settings', 'path'])
        if fields:
            self.trigger_computation(fields, operation=operation)

    @api.returns('self', lambda value: value.id)
    def copy(self, default=None):
        self.ensure_one()
        default = dict(default or [])
        names = []
        if self.is_root_directory:
            names = self.sudo().settings.root_directories.mapped('name')
            default.update({'settings': self.settings.id})
        elif 'parent_directory' in default:
            parent_directory = self.env['muk_dms.directory'].sudo().browse(
                default['parent_directory'])
            names = parent_directory.child_directories.mapped('name')
        else:
            names = self.sudo().parent_directory.child_directories.mapped(
                'name')
        default.update({'name': self.unique_name(self.name, names)})
        vals = self.copy_data(default)[0]
        new = self.with_context(lang=None).create(vals)
        self.copy_translations(new)
        for file in self.files:
            file.copy({'directory': new.id})
        for directory in self.child_directories:
            directory.copy({'parent_directory': new.id})
        return new

    def _before_unlink_record(self, operation):
        info = super(Directory, self)._before_unlink_record(operation)
        operation = self.env.context[
            'operation'] if 'operation' in self.env.context else operation
        if self.settings.system_locks and not 'operation' in self.env.context:
            info['lock_operation'] = operation
            self.lock_tree(operation=operation, lock_self=False)
        self.files.with_context(operation=operation).unlink()
        self.child_directories.with_context(operation=operation).unlink()
        return info

    def _after_unlink(self, result, info, infos, operation):
        super(Directory, self)._after_unlink(result, info, infos, operation)
        for info in infos:
            if 'lock_operation' in info:
                self.unlock_operation(info['lock_operation'])
コード例 #15
0
ファイル: stock_location.py プロジェクト: yemanadep/Flectra
class PushedFlow(models.Model):
    _name = "stock.location.path"
    _description = "Pushed Flow"
    _order = "sequence, name"

    name = fields.Char('Operation Name', required=True)
    company_id = fields.Many2one(
        'res.company', 'Company',
        default=lambda self: self.env['res.company']._company_default_get('stock.location.path'), index=True)
    route_id = fields.Many2one('stock.location.route', 'Route', required=True, ondelete='cascade')
    location_from_id = fields.Many2one(
        'stock.location', 'Source Location', index=True, ondelete='cascade', required=True,
        help="This rule can be applied when a move is confirmed that has this location as destination location")
    location_dest_id = fields.Many2one(
        'stock.location', 'Destination Location', index=True, ondelete='cascade', required=True,
        help="The new location where the goods need to go")
    delay = fields.Integer('Delay (days)', default=0, help="Number of days needed to transfer the goods")
    picking_type_id = fields.Many2one(
        'stock.picking.type', 'Operation Type', required=True,
        help="This is the operation type that will be put on the stock moves")
    auto = fields.Selection([
        ('manual', 'Manual Operation'),
        ('transparent', 'Automatic No Step Added')], string='Automatic Move',
        default='manual', index=True, required=True,
        help="The 'Manual Operation' value will create a stock move after the current one. "
             "With 'Automatic No Step Added', the location is replaced in the original move.")
    propagate = fields.Boolean('Propagate cancel and split', default=True, help='If checked, when the previous move is cancelled or split, the move generated by this move will too')
    active = fields.Boolean('Active', default=True)
    warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse')
    route_sequence = fields.Integer('Route Sequence', related='route_id.sequence', store=True)
    sequence = fields.Integer('Sequence')

    def _apply(self, move):
        new_date = (datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta.relativedelta(days=self.delay)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
        if self.auto == 'transparent':
            move.write({
                'date': new_date,
                'date_expected': new_date,
                'location_dest_id': self.location_dest_id.id})
            # avoid looping if a push rule is not well configured; otherwise call again push_apply to see if a next step is defined
            if self.location_dest_id != move.location_dest_id:
                # TDE FIXME: should probably be done in the move model IMO
                move._push_apply()
        else:
            new_move_vals = self._prepare_move_copy_values(move, new_date)
            new_move = move.copy(new_move_vals)
            move.write({'move_dest_ids': [(4, new_move.id)]})
            new_move._action_confirm()

    def _prepare_move_copy_values(self, move_to_copy, new_date):
        new_move_vals = {
                'origin': move_to_copy.origin or move_to_copy.picking_id.name or "/",
                'location_id': move_to_copy.location_dest_id.id,
                'location_dest_id': self.location_dest_id.id,
                'date': new_date,
                'date_expected': new_date,
                'company_id': self.company_id.id,
                'picking_id': False,
                'picking_type_id': self.picking_type_id.id,
                'propagate': self.propagate,
                'push_rule_id': self.id,
                'warehouse_id': self.warehouse_id.id,
            }

        return new_move_vals
コード例 #16
0
class LunchOrder(models.Model):
    """
    A lunch order contains one or more lunch order line(s). It is associated to a user for a given
    date. When creating a lunch order, applicable lunch alerts are displayed.
    """
    _name = 'lunch.order'
    _description = 'Lunch Order'
    _order = 'date desc'

    def _default_previous_order_ids(self):
        prev_order = self.env['lunch.order.line'].search(
            [('user_id', '=', self.env.uid),
             ('product_id.active', '!=', False)],
            limit=20,
            order='id desc')
        # If we return return prev_order.ids, we will have duplicates (identical orders).
        # Therefore, this following part removes duplicates based on product_id and note.
        return list({(order.product_id, order.note): order.id
                     for order in prev_order}.values())

    user_id = fields.Many2one('res.users',
                              'User',
                              readonly=True,
                              states={'new': [('readonly', False)]},
                              default=lambda self: self.env.uid)
    date = fields.Date('Date',
                       required=True,
                       readonly=True,
                       states={'new': [('readonly', False)]},
                       default=fields.Date.context_today)
    order_line_ids = fields.One2many('lunch.order.line',
                                     'order_id',
                                     'Products',
                                     readonly=True,
                                     copy=True,
                                     states={
                                         'new': [('readonly', False)],
                                         False: [('readonly', False)]
                                     })
    total = fields.Float(compute='_compute_total', string="Total", store=True)
    state = fields.Selection([('new', 'New'), ('confirmed', 'Received'),
                              ('cancelled', 'Cancelled')],
                             'Status',
                             readonly=True,
                             index=True,
                             copy=False,
                             compute='_compute_order_state',
                             store=True)
    alerts = fields.Text(compute='_compute_alerts_get', string="Alerts")
    company_id = fields.Many2one('res.company',
                                 related='user_id.company_id',
                                 store=True)
    currency_id = fields.Many2one('res.currency',
                                  related='company_id.currency_id',
                                  readonly=True,
                                  store=True)
    cash_move_balance = fields.Monetary(compute='_compute_cash_move_balance',
                                        multi='cash_move_balance')
    balance_visible = fields.Boolean(compute='_compute_cash_move_balance',
                                     multi='cash_move_balance')
    previous_order_ids = fields.Many2many('lunch.order.line',
                                          compute='_compute_previous_order')
    previous_order_widget = fields.Text(compute='_compute_previous_order')

    @api.one
    @api.depends('order_line_ids')
    def _compute_total(self):
        """
        get and sum the order lines' price
        """
        self.total = sum(orderline.price for orderline in self.order_line_ids)

    @api.multi
    def name_get(self):
        return [(order.id, '%s %s' % (_('Lunch Order'), '#%d' % order.id))
                for order in self]

    @api.depends('state')
    def _compute_alerts_get(self):
        """
        get the alerts to display on the order form
        """
        alert_msg = [
            alert.message for alert in self.env['lunch.alert'].search([])
            if alert.display
        ]

        if self.state == 'new':
            self.alerts = alert_msg and '\n'.join(alert_msg) or False

    @api.multi
    @api.depends('user_id', 'state')
    def _compute_previous_order(self):
        self.ensure_one()
        self.previous_order_widget = json.dumps(False)

        prev_order = self.env['lunch.order.line'].search(
            [('user_id', '=', self.env.uid),
             ('product_id.active', '!=', False)],
            limit=20,
            order='date desc, id desc')
        # If we use prev_order.ids, we will have duplicates (identical orders).
        # Therefore, this following part removes duplicates based on product_id and note.
        self.previous_order_ids = list({(order.product_id, order.note):
                                        order.id
                                        for order in prev_order}.values())

        if self.previous_order_ids:
            lunch_data = {}
            for line in self.previous_order_ids:
                lunch_data[line.id] = {
                    'line_id': line.id,
                    'product_id': line.product_id.id,
                    'product_name': line.product_id.name,
                    'supplier': line.supplier.name,
                    'note': line.note,
                    'price': line.price,
                    'date': line.date,
                    'currency_id': line.currency_id.id,
                }
            # sort the old lunch orders by (date, id)
            lunch_data = OrderedDict(
                sorted(lunch_data.items(),
                       key=lambda t: (t[1]['date'], t[0]),
                       reverse=True))
            self.previous_order_widget = json.dumps(lunch_data)

    @api.one
    @api.depends('user_id')
    def _compute_cash_move_balance(self):
        domain = [('user_id', '=', self.user_id.id)]
        lunch_cash = self.env['lunch.cashmove'].read_group(
            domain, ['amount', 'user_id'], ['user_id'])
        if len(lunch_cash):
            self.cash_move_balance = lunch_cash[0]['amount']
        self.balance_visible = (self.user_id
                                == self.env.user) or self.user_has_groups(
                                    'lunch.group_lunch_manager')

    @api.one
    @api.constrains('date')
    def _check_date(self):
        """
        Prevents the user to create an order in the past
        """
        date_order = datetime.datetime.strptime(self.date, '%Y-%m-%d')
        date_today = datetime.datetime.strptime(
            fields.Date.context_today(self), '%Y-%m-%d')
        if (date_order < date_today):
            raise ValidationError(_('The date of your order is in the past.'))

    @api.one
    @api.depends('order_line_ids.state')
    def _compute_order_state(self):
        """
        Update the state of lunch.order based on its orderlines. Here is the logic:
        - if at least one order line is cancelled, the order is set as cancelled
        - if no line is cancelled but at least one line is not confirmed, the order is set as new
        - if all lines are confirmed, the order is set as confirmed
        """
        if not self.order_line_ids:
            self.state = 'new'
        else:
            isConfirmed = True
            for orderline in self.order_line_ids:
                if orderline.state == 'cancelled':
                    self.state = 'cancelled'
                    return
                elif orderline.state == 'confirmed':
                    continue
                else:
                    isConfirmed = False

            if isConfirmed:
                self.state = 'confirmed'
            else:
                self.state = 'new'
        return
コード例 #17
0
ファイル: event.py プロジェクト: yemanadep/Flectra
class Event(models.Model):
    _name = 'event.event'
    _inherit = ['event.event', 'website.seo.metadata', 'website.published.mixin']

    website_published = fields.Boolean(track_visibility='onchange')
    website_id = fields.Many2one('website', string='Website', copy=False,
                                 default=lambda self: self.env.ref(
                                     'website.default_website'),
                                 help='Website in which '
                                      'Event will published.')

    is_participating = fields.Boolean("Is Participating", compute="_compute_is_participating")

    website_menu = fields.Boolean(
        'Dedicated Menu', compute='_compute_website_menu', inverse='_set_website_menu',
        help="Creates menus Introduction, Location and Register on the page "
             " of the event on the website.", store=True)
    menu_id = fields.Many2one('website.menu', 'Event Menu', copy=False)

    def _compute_is_participating(self):
        # we don't allow public user to see participating label
        if self.env.user != self.env.ref('base.public_user'):
            email = self.env.user.partner_id.email
            for event in self:
                domain = ['&', '|', ('email', '=', email), ('partner_id', '=', self.env.user.partner_id.id), ('event_id', '=', event.id)]
                event.is_participating = self.env['event.registration'].search_count(domain)

    @api.multi
    @api.depends('name')
    def _compute_website_url(self):
        super(Event, self)._compute_website_url()
        for event in self:
            if event.id:  # avoid to perform a slug on a not yet saved record in case of an onchange.
                event.website_url = '/event/%s' % slug(event)

    @api.onchange('event_type_id')
    def _onchange_type(self):
        super(Event, self)._onchange_type()
        if self.event_type_id:
            self.website_menu = self.event_type_id.website_menu

    def _get_standard_menu_entries_names(self):
        """ Method returning menu entries possibly generated by event modules.
        It is used to distinguish module-generated pages from custom pages. """
        return [_('Introduction'), _('Location'), _('Register')]

    def _get_menu_entries(self):
        """ Method returning menu entries to display on the website view of the
        event, possibly depending on some options in inheriting modules. """
        self.ensure_one()
        return [
            (_('Introduction'), False, 'website_event.template_intro'),
            (_('Location'), False, 'website_event.template_location'),
            (_('Register'), '/event/%s/register' % slug(self), False),
        ]

    @api.multi
    def _set_website_menu(self):
        for event in self:
            if event.menu_id and not event.website_menu:
                event.menu_id.unlink()
            elif event.website_menu:
                if not event.menu_id:
                    root_menu = self.env['website.menu'].create({'name': event.name})
                    event.menu_id = root_menu

                existing_page_names = event.menu_id.child_id.mapped('name')
                required_page_names = [entry[0] for entry in event._get_menu_entries()]
                standard_page_names = self._get_standard_menu_entries_names()

                # remove entries that should not exist anymore
                submenu_to_delete = event.menu_id.child_id.filtered(lambda menu: menu.name not in required_page_names and menu.name in standard_page_names)
                submenu_to_delete.unlink()

                # create missing entries
                for sequence, (name, url, xml_id) in enumerate(event._get_menu_entries()):
                    if name not in existing_page_names:
                        if not url:
                            newpath = self.env['website'].new_page(name + ' ' + event.name, template=xml_id, ispage=False)['url']
                            url = "/event/" + slug(event) + "/page/" + newpath[1:]
                        self.env['website.menu'].create({
                            'name': name,
                            'url': url,
                            'parent_id': event.menu_id.id,
                            'sequence': sequence,
                        })

    @api.multi
    def _compute_website_menu(self):
        for event in self:
            event.website_menu = bool(event.menu_id)

    @api.multi
    def google_map_img(self, zoom=8, width=298, height=298):
        self.ensure_one()
        if self.address_id:
            return self.sudo().address_id.google_map_img(zoom=zoom, width=width, height=height)
        return None

    @api.multi
    def google_map_link(self, zoom=8):
        self.ensure_one()
        if self.address_id:
            return self.sudo().address_id.google_map_link(zoom=zoom)
        return None

    @api.multi
    def _track_subtype(self, init_values):
        self.ensure_one()
        if 'website_published' in init_values and self.website_published:
            return 'website_event.mt_event_published'
        elif 'website_published' in init_values and not self.website_published:
            return 'website_event.mt_event_unpublished'
        return super(Event, self)._track_subtype(init_values)

    @api.multi
    def action_open_badge_editor(self):
        """ open the event badge editor : redirect to the report page of event badge report """
        self.ensure_one()
        return {
            'type': 'ir.actions.act_url',
            'target': 'new',
            'url': '/report/html/%s/%s?enable_editor' % ('event.event_event_report_template_badge', self.id),
        }
コード例 #18
0
class LunchOrderLine(models.Model):
    _name = 'lunch.order.line'
    _description = 'lunch order line'
    _order = 'date desc, id desc'

    name = fields.Char(related='product_id.name',
                       string="Product Name",
                       readonly=True)
    order_id = fields.Many2one('lunch.order',
                               'Order',
                               ondelete='cascade',
                               required=True)
    product_id = fields.Many2one('lunch.product', 'Product', required=True)
    category_id = fields.Many2one('lunch.product.category',
                                  string='Product Category',
                                  related='product_id.category_id',
                                  readonly=True,
                                  store=True)
    date = fields.Date(string='Date',
                       related='order_id.date',
                       readonly=True,
                       store=True)
    supplier = fields.Many2one('res.partner',
                               string='Vendor',
                               related='product_id.supplier',
                               readonly=True,
                               store=True)
    user_id = fields.Many2one('res.users',
                              string='User',
                              related='order_id.user_id',
                              readonly=True,
                              store=True)
    note = fields.Text('Note')
    price = fields.Float(related='product_id.price',
                         readonly=True,
                         store=True,
                         digits=dp.get_precision('Account'))
    state = fields.Selection([('new', 'New'), ('confirmed', 'Received'),
                              ('ordered', 'Ordered'),
                              ('cancelled', 'Cancelled')],
                             'Status',
                             readonly=True,
                             index=True,
                             default='new')
    cashmove = fields.One2many('lunch.cashmove', 'order_id', 'Cash Move')
    currency_id = fields.Many2one('res.currency',
                                  related='order_id.currency_id')

    @api.one
    def order(self):
        """
        The order_line is ordered to the vendor but isn't received yet
        """
        if self.user_has_groups("lunch.group_lunch_manager"):
            self.state = 'ordered'
        else:
            raise AccessError(
                _("Only your lunch manager processes the orders."))

    @api.one
    def confirm(self):
        """
        confirm one or more order line, update order status and create new cashmove
        """
        if self.user_has_groups("lunch.group_lunch_manager"):
            if self.state != 'confirmed':
                values = {
                    'user_id': self.user_id.id,
                    'amount': -self.price,
                    'description': self.product_id.name,
                    'order_id': self.id,
                    'state': 'order',
                    'date': self.date,
                }
                self.env['lunch.cashmove'].create(values)
                self.state = 'confirmed'
        else:
            raise AccessError(
                _("Only your lunch manager sets the orders as received."))

    @api.one
    def cancel(self):
        """
        cancel one or more order.line, update order status and unlink existing cashmoves
        """
        if self.user_has_groups("lunch.group_lunch_manager"):
            self.state = 'cancelled'
            self.cashmove.unlink()
        else:
            raise AccessError(_("Only your lunch manager cancels the orders."))
コード例 #19
0
class OpFacilityLine(models.Model):

    _inherit = 'op.facility.line'

    classroom_id = fields.Many2one('op.classroom', 'Classroom')
コード例 #20
0
ファイル: partner.py プロジェクト: mohshedid/odoo10_plus
class AccountFiscalPosition(models.Model):
    _name = 'account.fiscal.position'
    _description = 'Fiscal Position'
    _order = 'sequence'

    sequence = fields.Integer()
    name = fields.Char(string='Fiscal Position', required=True)
    active = fields.Boolean(
        default=True,
        help=
        "By unchecking the active field, you may hide a fiscal position without deleting it."
    )
    company_id = fields.Many2one('res.company', string='Company')
    account_ids = fields.One2many('account.fiscal.position.account',
                                  'position_id',
                                  string='Account Mapping',
                                  copy=True)
    tax_ids = fields.One2many('account.fiscal.position.tax',
                              'position_id',
                              string='Tax Mapping',
                              copy=True)
    note = fields.Text(
        'Notes',
        translate=True,
        help="Legal mentions that have to be printed on the invoices.")
    auto_apply = fields.Boolean(
        string='Detect Automatically',
        help="Apply automatically this fiscal position.")
    vat_required = fields.Boolean(
        string='VAT required', help="Apply only if partner has a VAT number.")
    country_id = fields.Many2one(
        'res.country',
        string='Country',
        help="Apply only if delivery or invoicing country match.")
    country_group_id = fields.Many2one(
        'res.country.group',
        string='Country Group',
        help="Apply only if delivery or invocing country match the group.")
    state_ids = fields.Many2many('res.country.state', string='Federal States')
    zip_from = fields.Integer(string='Zip Range From', default=0)
    zip_to = fields.Integer(string='Zip Range To', default=0)
    # To be used in hiding the 'Federal States' field('attrs' in view side) when selected 'Country' has 0 states.
    states_count = fields.Integer(compute='_compute_states_count')

    @api.one
    def _compute_states_count(self):
        self.states_count = len(self.country_id.state_ids)

    @api.one
    @api.constrains('zip_from', 'zip_to')
    def _check_zip(self):
        if self.zip_from > self.zip_to:
            raise ValidationError(
                _('Invalid "Zip Range", please configure it properly.'))
        return True

    @api.model  # noqa
    def map_tax(self, taxes, product=None, partner=None):
        result = self.env['account.tax'].browse()
        for tax in taxes:
            tax_count = 0
            for t in self.tax_ids:
                if t.tax_src_id == tax:
                    tax_count += 1
                    if t.tax_dest_id:
                        result |= t.tax_dest_id
            if not tax_count:
                result |= tax
        return result

    @api.model
    def map_account(self, account):
        for pos in self.account_ids:
            if pos.account_src_id == account:
                return pos.account_dest_id
        return account

    @api.model
    def map_accounts(self, accounts):
        """ Receive a dictionary having accounts in values and try to replace those accounts accordingly to the fiscal position.
        """
        ref_dict = {}
        for line in self.account_ids:
            ref_dict[line.account_src_id] = line.account_dest_id
        for key, acc in accounts.items():
            if acc in ref_dict:
                accounts[key] = ref_dict[acc]
        return accounts

    @api.onchange('country_id')
    def _onchange_country_id(self):
        if self.country_id:
            self.zip_from = self.zip_to = self.country_group_id = False
            self.state_ids = [(5, )]
            self.states_count = len(self.country_id.state_ids)

    @api.onchange('country_group_id')
    def _onchange_country_group_id(self):
        if self.country_group_id:
            self.zip_from = self.zip_to = self.country_id = False
            self.state_ids = [(5, )]

    @api.model
    def _get_fpos_by_region(self,
                            country_id=False,
                            state_id=False,
                            zipcode=False,
                            vat_required=False):
        if not country_id:
            return False
        base_domain = [('auto_apply', '=', True),
                       ('vat_required', '=', vat_required)]
        if self.env.context.get('force_company'):
            base_domain.append(
                ('company_id', '=', self.env.context.get('force_company')))
        null_state_dom = state_domain = [('state_ids', '=', False)]
        null_zip_dom = zip_domain = [('zip_from', '=', 0), ('zip_to', '=', 0)]
        null_country_dom = [('country_id', '=', False),
                            ('country_group_id', '=', False)]

        if zipcode and zipcode.isdigit():
            zipcode = int(zipcode)
            zip_domain = [('zip_from', '<=', zipcode),
                          ('zip_to', '>=', zipcode)]
        else:
            zipcode = 0

        if state_id:
            state_domain = [('state_ids', '=', state_id)]

        domain_country = base_domain + [('country_id', '=', country_id)]
        domain_group = base_domain + [
            ('country_group_id.country_ids', '=', country_id)
        ]

        # Build domain to search records with exact matching criteria
        fpos = self.search(domain_country + state_domain + zip_domain, limit=1)
        # return records that fit the most the criteria, and fallback on less specific fiscal positions if any can be found
        if not fpos and state_id:
            fpos = self.search(domain_country + null_state_dom + zip_domain,
                               limit=1)
        if not fpos and zipcode:
            fpos = self.search(domain_country + state_domain + null_zip_dom,
                               limit=1)
        if not fpos and state_id and zipcode:
            fpos = self.search(domain_country + null_state_dom + null_zip_dom,
                               limit=1)

        # fallback: country group with no state/zip range
        if not fpos:
            fpos = self.search(domain_group + null_state_dom + null_zip_dom,
                               limit=1)

        if not fpos:
            # Fallback on catchall (no country, no group)
            fpos = self.search(base_domain + null_country_dom, limit=1)
        return fpos or False

    @api.model
    def get_fiscal_position(self, partner_id, delivery_id=None):
        if not partner_id:
            return False
        # This can be easily overriden to apply more complex fiscal rules
        PartnerObj = self.env['res.partner']
        partner = PartnerObj.browse(partner_id)

        # if no delivery use invoicing
        if delivery_id:
            delivery = PartnerObj.browse(delivery_id)
        else:
            delivery = partner

        # partner manually set fiscal position always win
        if delivery.property_account_position_id or partner.property_account_position_id:
            return delivery.property_account_position_id.id or partner.property_account_position_id.id

        # First search only matching VAT positions
        vat_required = bool(partner.vat)
        fp = self._get_fpos_by_region(delivery.country_id.id,
                                      delivery.state_id.id, delivery.zip,
                                      vat_required)

        # Then if VAT required found no match, try positions that do not require it
        if not fp and vat_required:
            fp = self._get_fpos_by_region(delivery.country_id.id,
                                          delivery.state_id.id, delivery.zip,
                                          False)

        return fp.id if fp else False
コード例 #21
0
class ResPartnerBank(models.Model):
    _name = 'res.partner.bank'
    _rec_name = 'acc_number'
    _description = 'Bank Accounts'
    _order = 'sequence, id'

    @api.model
    def get_supported_account_types(self):
        return self._get_supported_account_types()

    @api.model
    def _get_supported_account_types(self):
        return [('bank', _('Normal'))]

    active = fields.Boolean(default=True)
    acc_type = fields.Selection(
        selection=lambda x: x.env['res.partner.bank'
                                  ].get_supported_account_types(),
        compute='_compute_acc_type',
        string='Type',
        help=
        'Bank account type: Normal or IBAN. Inferred from the bank account number.'
    )
    acc_number = fields.Char('Account Number', required=True)
    sanitized_acc_number = fields.Char(compute='_compute_sanitized_acc_number',
                                       string='Sanitized Account Number',
                                       readonly=True,
                                       store=True)
    acc_holder_name = fields.Char(
        string='Account Holder Name',
        help=
        "Account holder name, in case it is different than the name of the Account Holder"
    )
    partner_id = fields.Many2one(
        'res.partner',
        'Account Holder',
        ondelete='cascade',
        index=True,
        domain=['|', ('is_company', '=', True), ('parent_id', '=', False)],
        required=True)
    bank_id = fields.Many2one('res.bank', string='Bank')
    bank_name = fields.Char(related='bank_id.name', readonly=False)
    bank_bic = fields.Char(related='bank_id.bic', readonly=False)
    sequence = fields.Integer(default=10)
    currency_id = fields.Many2one('res.currency', string='Currency')
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 default=lambda self: self.env.company,
                                 ondelete='cascade',
                                 readonly=True)

    _sql_constraints = [
        ('unique_number', 'unique(sanitized_acc_number, company_id)',
         'Account Number must be unique'),
    ]

    @api.depends('acc_number')
    def _compute_sanitized_acc_number(self):
        for bank in self:
            bank.sanitized_acc_number = sanitize_account_number(
                bank.acc_number)

    @api.depends('acc_number')
    def _compute_acc_type(self):
        for bank in self:
            bank.acc_type = self.retrieve_acc_type(bank.acc_number)

    @api.model
    def retrieve_acc_type(self, acc_number):
        """ To be overridden by subclasses in order to support other account_types.
        """
        return 'bank'

    @api.model
    def _search(self,
                args,
                offset=0,
                limit=None,
                order=None,
                count=False,
                access_rights_uid=None):
        pos = 0
        while pos < len(args):
            # DLE P14
            if args[pos][0] == 'acc_number':
                op = args[pos][1]
                value = args[pos][2]
                if not isinstance(value, str) and isinstance(value, Iterable):
                    value = [sanitize_account_number(i) for i in value]
                else:
                    value = sanitize_account_number(value)
                if 'like' in op:
                    value = '%' + value + '%'
                args[pos] = ('sanitized_acc_number', op, value)
            pos += 1
        return super(ResPartnerBank,
                     self)._search(args,
                                   offset,
                                   limit,
                                   order,
                                   count=count,
                                   access_rights_uid=access_rights_uid)
コード例 #22
0
ファイル: partner.py プロジェクト: mohshedid/odoo10_plus
class ResPartner(models.Model):
    _name = 'res.partner'
    _inherit = 'res.partner'

    @api.multi
    def _credit_debit_get(self):
        tables, where_clause, where_params = self.env[
            'account.move.line']._query_get()
        where_params = [tuple(self.ids)] + where_params
        if where_clause:
            where_clause = 'AND ' + where_clause
        self._cr.execute(
            """SELECT account_move_line.partner_id, act.type, SUM(account_move_line.amount_residual)
                      FROM account_move_line
                      LEFT JOIN account_account a ON (account_move_line.account_id=a.id)
                      LEFT JOIN account_account_type act ON (a.user_type_id=act.id)
                      WHERE act.type IN ('receivable','payable')
                      AND account_move_line.partner_id IN %s
                      AND account_move_line.reconciled IS FALSE
                      """ + where_clause + """
                      GROUP BY account_move_line.partner_id, act.type
                      """, where_params)
        for pid, type, val in self._cr.fetchall():
            partner = self.browse(pid)
            if type == 'receivable':
                partner.credit = val
            elif type == 'payable':
                partner.debit = -val

    @api.multi
    def _asset_difference_search(self, account_type, operator, operand):
        if operator not in ('<', '=', '>', '>=', '<='):
            return []
        if type(operand) not in (float, int):
            return []
        sign = 1
        if account_type == 'payable':
            sign = -1
        res = self._cr.execute(
            '''
            SELECT partner.id
            FROM res_partner partner
            LEFT JOIN account_move_line aml ON aml.partner_id = partner.id
            RIGHT JOIN account_account acc ON aml.account_id = acc.id
            WHERE acc.internal_type = %s
              AND NOT acc.deprecated
            GROUP BY partner.id
            HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator +
            ''' %s''', (account_type, sign, operand))
        res = self._cr.fetchall()
        if not res:
            return [('id', '=', '0')]
        return [('id', 'in', [r[0] for r in res])]

    @api.model
    def _credit_search(self, operator, operand):
        return self._asset_difference_search('receivable', operator, operand)

    @api.model
    def _debit_search(self, operator, operand):
        return self._asset_difference_search('payable', operator, operand)

    @api.multi
    def _invoice_total(self):
        account_invoice_report = self.env['account.invoice.report']
        if not self.ids:
            self.total_invoiced = 0.0
            return True

        user_currency_id = self.env.user.company_id.currency_id.id
        all_partners_and_children = {}
        all_partner_ids = []
        for partner in self:
            # price_total is in the company currency
            all_partners_and_children[partner] = self.with_context(
                active_test=False).search([('id', 'child_of', partner.id)]).ids
            all_partner_ids += all_partners_and_children[partner]

        # searching account.invoice.report via the orm is comparatively expensive
        # (generates queries "id in []" forcing to build the full table).
        # In simple cases where all invoices are in the same currency than the user's company
        # access directly these elements

        # generate where clause to include multicompany rules
        where_query = account_invoice_report._where_calc([
            ('partner_id', 'in', all_partner_ids),
            ('state', 'not in', ['draft', 'cancel']),
            ('type', 'in', ('out_invoice', 'out_refund'))
        ])
        account_invoice_report._apply_ir_rules(where_query, 'read')
        from_clause, where_clause, where_clause_params = where_query.get_sql()

        # price_total is in the company currency
        query = """
                  SELECT SUM(price_total) as total, partner_id
                    FROM account_invoice_report account_invoice_report
                   WHERE %s
                   GROUP BY partner_id
                """ % where_clause
        self.env.cr.execute(query, where_clause_params)
        price_totals = self.env.cr.dictfetchall()
        for partner, child_ids in all_partners_and_children.items():
            partner.total_invoiced = sum(price['total']
                                         for price in price_totals
                                         if price['partner_id'] in child_ids)

    @api.multi
    def _compute_journal_item_count(self):
        AccountMoveLine = self.env['account.move.line']
        for partner in self:
            partner.journal_item_count = AccountMoveLine.search_count([
                ('partner_id', '=', partner.id)
            ])

    @api.multi
    def _compute_contracts_count(self):
        AccountAnalyticAccount = self.env['account.analytic.account']
        for partner in self:
            partner.contracts_count = AccountAnalyticAccount.search_count([
                ('partner_id', '=', partner.id)
            ])

    def get_followup_lines_domain(self,
                                  date,
                                  overdue_only=False,
                                  only_unblocked=False):
        domain = [('reconciled', '=', False),
                  ('account_id.deprecated', '=', False),
                  ('account_id.internal_type', '=', 'receivable'), '|',
                  ('debit', '!=', 0), ('credit', '!=', 0),
                  ('company_id', '=', self.env.user.company_id.id)]
        if only_unblocked:
            domain += [('blocked', '=', False)]
        if self.ids:
            if 'exclude_given_ids' in self._context:
                domain += [('partner_id', 'not in', self.ids)]
            else:
                domain += [('partner_id', 'in', self.ids)]
        #adding the overdue lines
        overdue_domain = [
            '|', '&', ('date_maturity', '!=', False),
            ('date_maturity', '<', date), '&', ('date_maturity', '=', False),
            ('date', '<', date)
        ]
        if overdue_only:
            domain += overdue_domain
        return domain

    @api.one
    def _compute_has_unreconciled_entries(self):
        # Avoid useless work if has_unreconciled_entries is not relevant for this partner
        if not self.active or not self.is_company and self.parent_id:
            return
        self.env.cr.execute(
            """ SELECT 1 FROM(
                    SELECT
                        p.last_time_entries_checked AS last_time_entries_checked,
                        MAX(l.write_date) AS max_date
                    FROM
                        account_move_line l
                        RIGHT JOIN account_account a ON (a.id = l.account_id)
                        RIGHT JOIN res_partner p ON (l.partner_id = p.id)
                    WHERE
                        p.id = %s
                        AND EXISTS (
                            SELECT 1
                            FROM account_move_line l
                            WHERE l.account_id = a.id
                            AND l.partner_id = p.id
                            AND l.amount_residual > 0
                        )
                        AND EXISTS (
                            SELECT 1
                            FROM account_move_line l
                            WHERE l.account_id = a.id
                            AND l.partner_id = p.id
                            AND l.amount_residual < 0
                        )
                    GROUP BY p.last_time_entries_checked
                ) as s
                WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked)
            """, (self.id, ))
        self.has_unreconciled_entries = self.env.cr.rowcount == 1

    @api.multi
    def mark_as_reconciled(self):
        self.env['account.partial.reconcile'].check_access_rights('write')
        return self.sudo().with_context(
            company_id=self.env.user.company_id.id).write({
                'last_time_entries_checked':
                time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
            })

    @api.one
    def _get_company_currency(self):
        if self.company_id:
            self.currency_id = self.sudo().company_id.currency_id
        else:
            self.currency_id = self.env.user.company_id.currency_id

    credit = fields.Monetary(compute='_credit_debit_get',
                             search=_credit_search,
                             string='Total Receivable',
                             help="Total amount this customer owes you.")
    debit = fields.Monetary(
        compute='_credit_debit_get',
        search=_debit_search,
        string='Total Payable',
        help="Total amount you have to pay to this vendor.")
    debit_limit = fields.Monetary('Payable Limit')
    total_invoiced = fields.Monetary(compute='_invoice_total',
                                     string="Total Invoiced",
                                     groups='account.group_account_invoice')
    currency_id = fields.Many2one(
        'res.currency',
        compute='_get_company_currency',
        readonly=True,
        string="Currency",
        help='Utility field to express amount currency')
    contracts_count = fields.Integer(compute='_compute_contracts_count',
                                     string="Contracts",
                                     type='integer')
    journal_item_count = fields.Integer(compute='_compute_journal_item_count',
                                        string="Journal Items",
                                        type="integer")
    property_account_payable_id = fields.Many2one(
        'account.account',
        company_dependent=True,
        string="Account Payable",
        oldname="property_account_payable",
        domain=
        "[('internal_type', '=', 'payable'), ('deprecated', '=', False)]",
        help=
        "This account will be used instead of the default one as the payable account for the current partner",
        required=True)
    property_account_receivable_id = fields.Many2one(
        'account.account',
        company_dependent=True,
        string="Account Receivable",
        oldname="property_account_receivable",
        domain=
        "[('internal_type', '=', 'receivable'), ('deprecated', '=', False)]",
        help=
        "This account will be used instead of the default one as the receivable account for the current partner",
        required=True)
    property_account_position_id = fields.Many2one(
        'account.fiscal.position',
        company_dependent=True,
        string="Fiscal Position",
        help=
        "The fiscal position will determine taxes and accounts used for the partner.",
        oldname="property_account_position")
    property_payment_term_id = fields.Many2one(
        'account.payment.term',
        company_dependent=True,
        string='Customer Payment Terms',
        help=
        "This payment term will be used instead of the default one for sales orders and customer invoices",
        oldname="property_payment_term")
    property_supplier_payment_term_id = fields.Many2one(
        'account.payment.term',
        company_dependent=True,
        string='Vendor Payment Terms',
        help=
        "This payment term will be used instead of the default one for purchase orders and vendor bills",
        oldname="property_supplier_payment_term")
    ref_company_ids = fields.One2many(
        'res.company',
        'partner_id',
        string='Companies that refers to partner',
        oldname="ref_companies")
    has_unreconciled_entries = fields.Boolean(
        compute='_compute_has_unreconciled_entries',
        help=
        "The partner has at least one unreconciled debit and credit since last time the invoices & payments matching was performed."
    )
    last_time_entries_checked = fields.Datetime(
        oldname='last_reconciliation_date',
        string='Latest Invoices & Payments Matching Date',
        readonly=True,
        copy=False,
        help=
        'Last time the invoices & payments matching was performed for this partner. '
        'It is set either if there\'s not at least an unreconciled debit and an unreconciled credit '
        'or if you click the "Done" button.')
    invoice_ids = fields.One2many('account.invoice',
                                  'partner_id',
                                  string='Invoices',
                                  readonly=True,
                                  copy=False)
    contract_ids = fields.One2many('account.analytic.account',
                                   'partner_id',
                                   string='Contracts',
                                   readonly=True)
    bank_account_count = fields.Integer(compute='_compute_bank_count',
                                        string="Bank")
    trust = fields.Selection([('good', 'Good Debtor'),
                              ('normal', 'Normal Debtor'),
                              ('bad', 'Bad Debtor')],
                             string='Degree of trust you have in this debtor',
                             default='normal',
                             company_dependent=True)
    invoice_warn = fields.Selection(WARNING_MESSAGE,
                                    'Invoice',
                                    help=WARNING_HELP,
                                    required=True,
                                    default="no-message")
    invoice_warn_msg = fields.Text('Message for Invoice')

    @api.multi
    def _compute_bank_count(self):
        bank_data = self.env['res.partner.bank'].read_group(
            [('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id'])
        mapped_data = dict([(bank['partner_id'][0], bank['partner_id_count'])
                            for bank in bank_data])
        for partner in self:
            partner.bank_account_count = mapped_data.get(partner.id, 0)

    def _find_accounting_partner(self, partner):
        ''' Find the partner for which the accounting entries will be created '''
        return partner.commercial_partner_id

    @api.model
    def _commercial_fields(self):
        return super(ResPartner, self)._commercial_fields() + \
            ['debit_limit', 'property_account_payable_id', 'property_account_receivable_id', 'property_account_position_id',
             'property_payment_term_id', 'property_supplier_payment_term_id', 'last_time_entries_checked']

    @api.multi
    def action_view_partner_invoices(self):
        self.ensure_one()
        action = self.env.ref(
            'account.action_invoice_refund_out_tree').read()[0]
        action['domain'] = literal_eval(action['domain'])
        action['domain'].append(('partner_id', 'child_of', self.id))
        return action
コード例 #23
0
class WebsiteVisitor(models.Model):
    _name = 'website.visitor'
    _description = 'Website Visitor'
    _order = 'last_connection_datetime DESC'

    name = fields.Char('Name')
    access_token = fields.Char(required=True,
                               default=lambda x: uuid.uuid4().hex,
                               index=False,
                               copy=False,
                               groups='base.group_website_publisher')
    active = fields.Boolean('Active', default=True)
    website_id = fields.Many2one('website', "Website", readonly=True)
    partner_id = fields.Many2one('res.partner',
                                 string="Linked Partner",
                                 help="Partner of the last logged in user.")
    partner_image = fields.Binary(related='partner_id.image_1920')

    # localisation and info
    country_id = fields.Many2one('res.country', 'Country', readonly=True)
    country_flag = fields.Char(related="country_id.image_url",
                               string="Country Flag")
    lang_id = fields.Many2one(
        'res.lang',
        string='Language',
        help="Language from the website when visitor has been created")
    timezone = fields.Selection(_tz_get, string='Timezone')
    email = fields.Char(string='Email', compute='_compute_email_phone')
    mobile = fields.Char(string='Mobile Phone', compute='_compute_email_phone')

    # Visit fields
    visit_count = fields.Integer(
        'Number of visits',
        default=1,
        readonly=True,
        help=
        "A new visit is considered if last connection was more than 8 hours ago."
    )
    website_track_ids = fields.One2many('website.track',
                                        'visitor_id',
                                        string='Visited Pages History',
                                        readonly=True)
    visitor_page_count = fields.Integer(
        'Page Views',
        compute="_compute_page_statistics",
        help="Total number of visits on tracked pages")
    page_ids = fields.Many2many('website.page',
                                string="Visited Pages",
                                compute="_compute_page_statistics",
                                groups="website.group_website_designer")
    page_count = fields.Integer('# Visited Pages',
                                compute="_compute_page_statistics",
                                help="Total number of tracked page visited")
    last_visited_page_id = fields.Many2one(
        'website.page',
        string="Last Visited Page",
        compute="_compute_last_visited_page_id")

    # Time fields
    create_date = fields.Datetime('First connection date', readonly=True)
    last_connection_datetime = fields.Datetime('Last Connection',
                                               default=fields.Datetime.now,
                                               help="Last page view date",
                                               readonly=True)
    time_since_last_action = fields.Char(
        'Last action',
        compute="_compute_time_statistics",
        help='Time since last page view. E.g.: 2 minutes ago')
    is_connected = fields.Boolean(
        'Is connected ?',
        compute='_compute_time_statistics',
        help=
        'A visitor is considered as connected if his last page view was within the last 5 minutes.'
    )

    _sql_constraints = [
        ('access_token_unique', 'unique(access_token)',
         'Access token should be unique.'),
        ('partner_uniq', 'unique(partner_id)',
         'A partner is linked to only one visitor.'),
    ]

    @api.depends('name')
    def name_get(self):
        return [(record.id, (record.name
                             or _('Website Visitor #%s', record.id)))
                for record in self]

    @api.depends('partner_id.email_normalized', 'partner_id.mobile',
                 'partner_id.phone')
    def _compute_email_phone(self):
        results = self.env['res.partner'].search_read(
            [('id', 'in', self.partner_id.ids)],
            ['id', 'email_normalized', 'mobile', 'phone'],
        )
        mapped_data = {
            result['id']: {
                'email_normalized': result['email_normalized'],
                'mobile':
                result['mobile'] if result['mobile'] else result['phone']
            }
            for result in results
        }

        for visitor in self:
            visitor.email = mapped_data.get(visitor.partner_id.id,
                                            {}).get('email_normalized')
            visitor.mobile = mapped_data.get(visitor.partner_id.id,
                                             {}).get('mobile')

    @api.depends('website_track_ids')
    def _compute_page_statistics(self):
        results = self.env['website.track'].read_group(
            [('visitor_id', 'in', self.ids), ('url', '!=', False)],
            ['visitor_id', 'page_id', 'url'], ['visitor_id', 'page_id', 'url'],
            lazy=False)
        mapped_data = {}
        for result in results:
            visitor_info = mapped_data.get(result['visitor_id'][0], {
                'page_count': 0,
                'visitor_page_count': 0,
                'page_ids': set()
            })
            visitor_info['visitor_page_count'] += result['__count']
            visitor_info['page_count'] += 1
            if result['page_id']:
                visitor_info['page_ids'].add(result['page_id'][0])
            mapped_data[result['visitor_id'][0]] = visitor_info

        for visitor in self:
            visitor_info = mapped_data.get(visitor.id, {
                'page_count': 0,
                'visitor_page_count': 0,
                'page_ids': set()
            })
            visitor.page_ids = [(6, 0, visitor_info['page_ids'])]
            visitor.visitor_page_count = visitor_info['visitor_page_count']
            visitor.page_count = visitor_info['page_count']

    @api.depends('website_track_ids.page_id')
    def _compute_last_visited_page_id(self):
        results = self.env['website.track'].read_group(
            [('visitor_id', 'in', self.ids)],
            ['visitor_id', 'page_id', 'visit_datetime:max'],
            ['visitor_id', 'page_id'],
            lazy=False)
        mapped_data = {
            result['visitor_id'][0]: result['page_id'][0]
            for result in results if result['page_id']
        }
        for visitor in self:
            visitor.last_visited_page_id = mapped_data.get(visitor.id, False)

    @api.depends('last_connection_datetime')
    def _compute_time_statistics(self):
        for visitor in self:
            visitor.time_since_last_action = _format_time_ago(
                self.env, (datetime.now() - visitor.last_connection_datetime))
            visitor.is_connected = (
                datetime.now() -
                visitor.last_connection_datetime) < timedelta(minutes=5)

    def _check_for_message_composer(self):
        """ Purpose of this method is to actualize visitor model prior to contacting
        him. Used notably for inheritance purpose, when dealing with leads that
        could update the visitor model. """
        return bool(self.partner_id and self.partner_id.email)

    def _prepare_message_composer_context(self):
        return {
            'default_model': 'res.partner',
            'default_res_id': self.partner_id.id,
            'default_partner_ids': [self.partner_id.id],
        }

    def action_send_mail(self):
        self.ensure_one()
        if not self._check_for_message_composer():
            raise UserError(
                _("There are no contact and/or no email linked to this visitor."
                  ))
        visitor_composer_ctx = self._prepare_message_composer_context()
        compose_form = self.env.ref('mail.email_compose_message_wizard_form',
                                    False)
        compose_ctx = dict(
            default_use_template=False,
            default_composition_mode='comment',
        )
        compose_ctx.update(**visitor_composer_ctx)
        return {
            'name': _('Contact Visitor'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'mail.compose.message',
            'views': [(compose_form.id, 'form')],
            'view_id': compose_form.id,
            'target': 'new',
            'context': compose_ctx,
        }

    def _get_visitor_from_request(self, force_create=False):
        """ Return the visitor as sudo from the request if there is a visitor_uuid cookie.
            It is possible that the partner has changed or has disconnected.
            In that case the cookie is still referencing the old visitor and need to be replaced
            with the one of the visitor returned !!!. """

        # This function can be called in json with mobile app.
        # In case of mobile app, no uid is set on the jsonRequest env.
        # In case of multi db, _env is None on request, and request.env unbound.
        if not request:
            return None
        Visitor = self.env['website.visitor'].sudo()
        visitor = Visitor
        access_token = request.httprequest.cookies.get('visitor_uuid')
        if access_token:
            visitor = Visitor.with_context(active_test=False).search([
                ('access_token', '=', access_token)
            ])
            # Prefetch access_token and other fields. Since access_token has a restricted group and we access
            # a non restricted field (partner_id) first it is not fetched and will require an additional query to be retrieved.
            visitor.access_token

        if not self.env.user._is_public():
            partner_id = self.env.user.partner_id
            if not visitor or visitor.partner_id and visitor.partner_id != partner_id:
                # Partner and no cookie or wrong cookie
                visitor = Visitor.with_context(active_test=False).search([
                    ('partner_id', '=', partner_id.id)
                ])
        elif visitor and visitor.partner_id:
            # Cookie associated to a Partner
            visitor = Visitor

        if visitor and not visitor.timezone:
            tz = self._get_visitor_timezone()
            if tz:
                visitor.timezone = tz
        if not visitor and force_create:
            visitor = self._create_visitor()

        return visitor

    def _handle_webpage_dispatch(self, response, website_page):
        # get visitor. Done here to avoid having to do it multiple times in case of override.
        visitor_sudo = self._get_visitor_from_request(force_create=True)
        if request.httprequest.cookies.get('visitor_uuid',
                                           '') != visitor_sudo.access_token:
            expiration_date = datetime.now() + timedelta(days=365)
            response.set_cookie('visitor_uuid',
                                visitor_sudo.access_token,
                                expires=expiration_date)
        self._handle_website_page_visit(website_page, visitor_sudo)

    def _handle_website_page_visit(self, website_page, visitor_sudo):
        """ Called on dispatch. This will create a website.visitor if the http request object
        is a tracked website page or a tracked view. Only on tracked elements to avoid having
        too much operations done on every page or other http requests.
        Note: The side effect is that the last_connection_datetime is updated ONLY on tracked elements."""
        url = request.httprequest.url
        website_track_values = {
            'url': url,
            'visit_datetime': datetime.now(),
        }
        if website_page:
            website_track_values['page_id'] = website_page.id
            domain = [('page_id', '=', website_page.id)]
        else:
            domain = [('url', '=', url)]
        visitor_sudo._add_tracking(domain, website_track_values)
        if visitor_sudo.lang_id.id != request.lang.id:
            visitor_sudo.write({'lang_id': request.lang.id})

    def _add_tracking(self, domain, website_track_values):
        """ Add the track and update the visitor"""
        domain = expression.AND([domain, [('visitor_id', '=', self.id)]])
        last_view = self.env['website.track'].sudo().search(domain, limit=1)
        if not last_view or last_view.visit_datetime < datetime.now(
        ) - timedelta(minutes=30):
            website_track_values['visitor_id'] = self.id
            self.env['website.track'].create(website_track_values)
        self._update_visitor_last_visit()

    def _create_visitor(self):
        """ Create a visitor. Tracking is added after the visitor has been created."""
        country_code = request.session.get('geoip',
                                           {}).get('country_code', False)
        country_id = request.env['res.country'].sudo().search(
            [('code', '=',
              country_code)], limit=1).id if country_code else False
        vals = {
            'lang_id': request.lang.id,
            'country_id': country_id,
            'website_id': request.website.id,
        }

        tz = self._get_visitor_timezone()
        if tz:
            vals['timezone'] = tz

        if not self.env.user._is_public():
            vals['partner_id'] = self.env.user.partner_id.id
            vals['name'] = self.env.user.partner_id.name
        return self.sudo().create(vals)

    def _link_to_partner(self, partner, update_values=None):
        """ Link visitors to a partner. This method is meant to be overridden in
        order to propagate, if necessary, partner information to sub records.

        :param partner: partner used to link sub records;
        :param update_values: optional values to update visitors to link;
        """
        vals = {'name': partner.name}
        if update_values:
            vals.update(update_values)
        self.write(vals)

    def _link_to_visitor(self, target, keep_unique=True):
        """ Link visitors to target visitors, because they are linked to the
        same identity. Purpose is mainly to propagate partner identity to sub
        records to ease database update and decide what to do with "duplicated".
        THis method is meant to be overridden in order to implement some specific
        behavior linked to sub records of duplicate management.

        :param target: main visitor, target of link process;
        :param keep_unique: if True, find a way to make target unique;
        """
        # Link sub records of self to target partner
        if target.partner_id:
            self._link_to_partner(target.partner_id)
        # Link sub records of self to target visitor
        self.website_track_ids.write({'visitor_id': target.id})

        if keep_unique:
            self.unlink()

        return target

    def _cron_archive_visitors(self):
        delay_days = int(self.env['ir.config_parameter'].sudo().get_param(
            'website.visitor.live.days', 30))
        deadline = datetime.now() - timedelta(days=delay_days)
        visitors_to_archive = self.env['website.visitor'].sudo().search([
            ('last_connection_datetime', '<', deadline)
        ])
        visitors_to_archive.write({'active': False})

    def _update_visitor_last_visit(self):
        """ We need to do this part here to avoid concurrent updates error. """
        try:
            with self.env.cr.savepoint():
                query_lock = "SELECT * FROM website_visitor where id = %s FOR NO KEY UPDATE NOWAIT"
                self.env.cr.execute(query_lock, (self.id, ),
                                    log_exceptions=False)

                date_now = datetime.now()
                query = "UPDATE website_visitor SET "
                if self.last_connection_datetime < (date_now -
                                                    timedelta(hours=8)):
                    query += "visit_count = visit_count + 1,"
                query += """
                    active = True,
                    last_connection_datetime = %s
                    WHERE id = %s
                """
                self.env.cr.execute(query, (date_now, self.id),
                                    log_exceptions=False)
        except Exception:
            pass

    def _get_visitor_timezone(self):
        tz = request.httprequest.cookies.get('tz') if request else None
        if tz in pytz.all_timezones:
            return tz
        elif not self.env.user._is_public():
            return self.env.user.tz
        else:
            return None
コード例 #24
0
class PosConfig(models.Model):
    _name = 'pos.config'

    def _default_sale_journal(self):
        journal = self.env.ref('point_of_sale.pos_sale_journal',
                               raise_if_not_found=False)
        if journal and journal.sudo().company_id == self.env.user.company_id:
            return journal
        return self._default_invoice_journal()

    def _default_invoice_journal(self):
        return self.env['account.journal'].search(
            [('type', '=', 'sale'),
             ('company_id', '=', self.env.user.company_id.id)],
            limit=1)

    def _default_pricelist(self):
        return self.env['product.pricelist'].search(
            [('currency_id', '=', self.env.user.company_id.currency_id.id)],
            limit=1)

    def _get_default_location(self):
        return self.env['stock.warehouse'].search(
            [('company_id', '=', self.env.user.company_id.id)],
            limit=1).lot_stock_id

    def _get_group_pos_manager(self):
        return self.env.ref('point_of_sale.group_pos_manager')

    def _get_group_pos_user(self):
        return self.env.ref('point_of_sale.group_pos_user')

    def _compute_default_customer_html(self):
        return self.env['ir.qweb'].render(
            'point_of_sale.customer_facing_display_html')

    name = fields.Char(string='Point of Sale Name',
                       index=True,
                       required=True,
                       help="An internal identification of the point of sale.")
    is_installed_account_accountant = fields.Boolean(
        compute="_compute_is_installed_account_accountant")
    journal_ids = fields.Many2many(
        'account.journal',
        'pos_config_journal_rel',
        'pos_config_id',
        'journal_id',
        string='Available Payment Methods',
        domain=
        "[('journal_user', '=', True ), ('type', 'in', ['bank', 'cash'])]",
    )
    picking_type_id = fields.Many2one('stock.picking.type',
                                      string='Operation Type')
    use_existing_lots = fields.Boolean(
        related='picking_type_id.use_existing_lots')
    stock_location_id = fields.Many2one('stock.location',
                                        string='Stock Location',
                                        domain=[('usage', '=', 'internal')],
                                        required=True,
                                        default=_get_default_location)
    journal_id = fields.Many2one(
        'account.journal',
        string='Sales Journal',
        domain=[('type', '=', 'sale')],
        help="Accounting journal used to post sales entries.",
        default=_default_sale_journal)
    invoice_journal_id = fields.Many2one(
        'account.journal',
        string='Invoice Journal',
        domain=[('type', '=', 'sale')],
        help="Accounting journal used to create invoices.",
        default=_default_invoice_journal)
    currency_id = fields.Many2one('res.currency',
                                  compute='_compute_currency',
                                  string="Currency")
    iface_cashdrawer = fields.Boolean(
        string='Cashdrawer', help="Automatically open the cashdrawer.")
    iface_payment_terminal = fields.Boolean(
        string='Payment Terminal',
        help="Enables Payment Terminal integration.")
    iface_electronic_scale = fields.Boolean(
        string='Electronic Scale',
        help="Enables Electronic Scale integration.")
    iface_vkeyboard = fields.Boolean(
        string='Virtual KeyBoard',
        help=
        u"Don’t turn this option on if you take orders on smartphones or tablets. \n Such devices already benefit from a native keyboard."
    )
    iface_customer_facing_display = fields.Boolean(
        string='Customer Facing Display',
        help="Show checkout to customers with a remotely-connected screen.")
    iface_print_via_proxy = fields.Boolean(
        string='Print via Proxy',
        help="Bypass browser printing and prints via the hardware proxy.")
    iface_scan_via_proxy = fields.Boolean(
        string='Scan via Proxy',
        help=
        "Enable barcode scanning with a remotely connected barcode scanner.")
    iface_invoicing = fields.Boolean(
        string='Invoicing',
        help='Enables invoice generation from the Point of Sale.')
    iface_big_scrollbars = fields.Boolean(
        'Large Scrollbars', help='For imprecise industrial touchscreens.')
    iface_print_auto = fields.Boolean(
        string='Automatic Receipt Printing',
        default=False,
        help=
        'The receipt will automatically be printed at the end of each order.')
    iface_print_skip_screen = fields.Boolean(
        string='Skip Preview Screen',
        default=True,
        help=
        'The receipt screen will be skipped if the receipt can be printed automatically.'
    )
    iface_precompute_cash = fields.Boolean(
        string='Prefill Cash Payment',
        help=
        'The payment input will behave similarily to bank payment input, and will be prefilled with the exact due amount.'
    )
    iface_tax_included = fields.Selection([('subtotal', 'Tax-Excluded Prices'),
                                           ('total', 'Tax-Included Prices')],
                                          "Tax Display",
                                          default='subtotal',
                                          required=True)
    iface_start_categ_id = fields.Many2one(
        'pos.category',
        string='Initial Category',
        help=
        'The point of sale will display this product category by default. If no category is specified, all available products will be shown.'
    )
    iface_display_categ_images = fields.Boolean(
        string='Display Category Pictures',
        help="The product categories will be displayed with pictures.")
    restrict_price_control = fields.Boolean(
        string='Restrict Price Modifications to Managers',
        help=
        "Only users with Manager access rights for PoS app can modify the product prices on orders."
    )
    cash_control = fields.Boolean(
        string='Cash Control',
        help="Check the amount of the cashbox at opening and closing.")
    receipt_header = fields.Text(
        string='Receipt Header',
        help=
        "A short text that will be inserted as a header in the printed receipt."
    )
    receipt_footer = fields.Text(
        string='Receipt Footer',
        help=
        "A short text that will be inserted as a footer in the printed receipt."
    )
    proxy_ip = fields.Char(
        string='IP Address',
        size=45,
        help=
        'The hostname or ip address of the hardware proxy, Will be autodetected if left empty.'
    )
    active = fields.Boolean(default=True)
    uuid = fields.Char(
        readonly=True,
        default=lambda self: str(uuid.uuid4()),
        help=
        'A globally unique identifier for this pos configuration, used to prevent conflicts in client-generated data.'
    )
    sequence_id = fields.Many2one(
        'ir.sequence',
        string='Order IDs Sequence',
        readonly=True,
        help=
        "This sequence is automatically created by Flectra but you can change it "
        "to customize the reference numbers of your orders.",
        copy=False)
    sequence_line_id = fields.Many2one(
        'ir.sequence',
        string='Order Line IDs Sequence',
        readonly=True,
        help=
        "This sequence is automatically created by Flectra but you can change it "
        "to customize the reference numbers of your orders lines.",
        copy=False)
    session_ids = fields.One2many('pos.session',
                                  'config_id',
                                  string='Sessions')
    current_session_id = fields.Many2one('pos.session',
                                         compute='_compute_current_session',
                                         string="Current Session")
    current_session_state = fields.Char(compute='_compute_current_session')
    last_session_closing_cash = fields.Float(compute='_compute_last_session')
    last_session_closing_date = fields.Date(compute='_compute_last_session')
    pos_session_username = fields.Char(compute='_compute_current_session_user')
    pos_session_state = fields.Char(compute='_compute_current_session_user')
    group_by = fields.Boolean(
        string='Group Journal Items',
        default=True,
        help=
        "Check this if you want to group the Journal Items by Product while closing a Session."
    )
    pricelist_id = fields.Many2one(
        'product.pricelist',
        string='Default Pricelist',
        required=True,
        default=_default_pricelist,
        help=
        "The pricelist used if no customer is selected or if the customer has no Sale Pricelist configured."
    )
    available_pricelist_ids = fields.Many2many(
        'product.pricelist',
        string='Available Pricelists',
        default=_default_pricelist,
        help=
        "Make several pricelists available in the Point of Sale. You can also apply a pricelist to specific customers from their contact form (in Sales tab). To be valid, this pricelist must be listed here as an available pricelist. Otherwise the default pricelist will apply."
    )
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 required=True,
                                 default=lambda self: self.env.user.company_id)
    barcode_nomenclature_id = fields.Many2one(
        'barcode.nomenclature',
        string='Barcode Nomenclature',
        help=
        'Defines what kind of barcodes are available and how they are assigned to products, customers and cashiers.'
    )
    group_pos_manager_id = fields.Many2one(
        'res.groups',
        string='Point of Sale Manager Group',
        default=_get_group_pos_manager,
        help=
        'This field is there to pass the id of the pos manager group to the point of sale client.'
    )
    group_pos_user_id = fields.Many2one(
        'res.groups',
        string='Point of Sale User Group',
        default=_get_group_pos_user,
        help=
        'This field is there to pass the id of the pos user group to the point of sale client.'
    )
    iface_tipproduct = fields.Boolean(string="Product tips")
    tip_product_id = fields.Many2one(
        'product.product',
        string='Tip Product',
        help="This product is used as reference on customer receipts.")
    fiscal_position_ids = fields.Many2many(
        'account.fiscal.position',
        string='Fiscal Positions',
        help=
        'This is useful for restaurants with onsite and take-away services that imply specific tax rates.'
    )
    default_fiscal_position_id = fields.Many2one(
        'account.fiscal.position', string='Default Fiscal Position')
    default_cashbox_lines_ids = fields.One2many('account.cashbox.line',
                                                'default_pos_id',
                                                string='Default Balance')
    customer_facing_display_html = fields.Html(
        string='Customer facing display content',
        translate=True,
        default=_compute_default_customer_html)
    use_pricelist = fields.Boolean("Use a pricelist.")
    group_sale_pricelist = fields.Boolean(
        "Use pricelists to adapt your price per customers",
        implied_group='product.group_sale_pricelist',
        help=
        """Allows to manage different prices based on rules per category of customers.
                    Example: 10% for retailers, promotion of 5 EUR on this product, etc."""
    )
    group_pricelist_item = fields.Boolean(
        "Show pricelists to customers",
        implied_group='product.group_pricelist_item')
    tax_regime = fields.Boolean("Tax Regime")
    tax_regime_selection = fields.Boolean("Tax Regime Selection value")
    barcode_scanner = fields.Boolean("Barcode Scanner")
    start_category = fields.Boolean("Set Start Category")
    module_pos_restaurant = fields.Boolean("Is a Bar/Restaurant")
    module_pos_discount = fields.Boolean("Global Discounts")
    module_pos_mercury = fields.Boolean(string="Integrated Card Payments")
    module_pos_reprint = fields.Boolean(string="Reprint Receipt")
    is_posbox = fields.Boolean("PosBox")
    is_header_or_footer = fields.Boolean("Header & Footer")

    def _compute_is_installed_account_accountant(self):
        account_accountant = self.env['ir.module.module'].sudo().search([
            ('name', '=', 'account_accountant'), ('state', '=', 'installed')
        ])
        for pos_config in self:
            pos_config.is_installed_account_accountant = account_accountant and account_accountant.id

    @api.depends('journal_id.currency_id', 'journal_id.company_id.currency_id')
    def _compute_currency(self):
        for pos_config in self:
            if pos_config.journal_id:
                pos_config.currency_id = pos_config.journal_id.currency_id.id or pos_config.journal_id.company_id.currency_id.id
            else:
                pos_config.currency_id = self.env.user.company_id.currency_id.id

    @api.depends('session_ids')
    def _compute_current_session(self):
        for pos_config in self:
            session = pos_config.session_ids.filtered(lambda r: r.user_id.id == self.env.uid and \
                not r.state == 'closed' and \
                not r.rescue)
            # sessions ordered by id desc
            pos_config.current_session_id = session and session[0].id or False
            pos_config.current_session_state = session and session[
                0].state or False

    @api.depends('session_ids')
    def _compute_last_session(self):
        PosSession = self.env['pos.session']
        for pos_config in self:
            session = PosSession.search_read(
                [('config_id', '=', pos_config.id), ('state', '=', 'closed')],
                ['cash_register_balance_end_real', 'stop_at'],
                order="stop_at desc",
                limit=1)
            if session:
                pos_config.last_session_closing_cash = session[0][
                    'cash_register_balance_end_real']
                pos_config.last_session_closing_date = session[0]['stop_at']
            else:
                pos_config.last_session_closing_cash = 0
                pos_config.last_session_closing_date = False

    @api.depends('session_ids')
    def _compute_current_session_user(self):
        for pos_config in self:
            session = pos_config.session_ids.filtered(lambda s: s.state in [
                'opening_control', 'opened', 'closing_control'
            ] and not s.rescue)
            pos_config.pos_session_username = session and session[
                0].user_id.name or False
            pos_config.pos_session_state = session and session[0].state or False

    @api.constrains('company_id', 'stock_location_id')
    def _check_company_location(self):
        if self.stock_location_id.company_id and self.stock_location_id.company_id.id != self.company_id.id:
            raise ValidationError(
                _("The company of the stock location is different than the one of point of sale"
                  ))

    @api.constrains('company_id', 'journal_id')
    def _check_company_journal(self):
        if self.journal_id and self.journal_id.company_id.id != self.company_id.id:
            raise ValidationError(
                _("The company of the sales journal is different than the one of point of sale"
                  ))

    @api.constrains('company_id', 'invoice_journal_id')
    def _check_company_invoice_journal(self):
        if self.invoice_journal_id and self.invoice_journal_id.company_id.id != self.company_id.id:
            raise ValidationError(
                _("The invoice journal and the point of sale must belong to the same company"
                  ))

    @api.constrains('company_id', 'journal_ids')
    def _check_company_payment(self):
        if self.env['account.journal'].search_count([
            ('id', 'in', self.journal_ids.ids),
            ('company_id', '!=', self.company_id.id)
        ]):
            raise ValidationError(
                _("The company of a payment method is different than the one of point of sale"
                  ))

    @api.constrains('pricelist_id', 'available_pricelist_ids', 'journal_id',
                    'invoice_journal_id', 'journal_ids')
    def _check_currencies(self):
        if self.pricelist_id not in self.available_pricelist_ids:
            raise ValidationError(
                _("The default pricelist must be included in the available pricelists."
                  ))
        if any(
                self.available_pricelist_ids.mapped(
                    lambda pricelist: pricelist.currency_id != self.currency_id
                )):
            raise ValidationError(
                _("All available pricelists must be in the same currency as the company or"
                  " as the Sales Journal set on this point of sale if you use"
                  " the Accounting application."))
        if self.invoice_journal_id.currency_id and self.invoice_journal_id.currency_id != self.currency_id:
            raise ValidationError(
                _("The invoice journal must be in the same currency as the Sales Journal or the company currency if that is not set."
                  ))
        if any(
                self.journal_ids.mapped(
                    lambda journal: journal.currency_id and journal.currency_id
                    != self.currency_id)):
            raise ValidationError(
                _("All payment methods must be in the same currency as the Sales Journal or the company currency if that is not set."
                  ))

    @api.onchange('iface_print_via_proxy')
    def _onchange_iface_print_via_proxy(self):
        self.iface_print_auto = self.iface_print_via_proxy

    @api.onchange('picking_type_id')
    def _onchange_picking_type_id(self):
        if self.picking_type_id.default_location_src_id.usage == 'internal' and self.picking_type_id.default_location_dest_id.usage == 'customer':
            self.stock_location_id = self.picking_type_id.default_location_src_id.id

    @api.onchange('use_pricelist')
    def _onchange_use_pricelist(self):
        """
        If the 'pricelist' box is unchecked, we reset the pricelist_id to stop
        using a pricelist for this posbox. 
        """
        if not self.use_pricelist:
            self.pricelist_id = self._default_pricelist()
        else:
            self.update({
                'group_sale_pricelist': True,
                'group_pricelist_item': True,
            })

    @api.onchange('available_pricelist_ids')
    def _onchange_available_pricelist_ids(self):
        if self.pricelist_id not in self.available_pricelist_ids:
            self.pricelist_id = False

    @api.onchange('iface_scan_via_proxy')
    def _onchange_iface_scan_via_proxy(self):
        if self.iface_scan_via_proxy:
            self.barcode_scanner = True
        else:
            self.barcode_scanner = False

    @api.onchange('barcode_scanner')
    def _onchange_barcode_scanner(self):
        if self.barcode_scanner:
            self.barcode_nomenclature_id = self.env[
                'barcode.nomenclature'].search([], limit=1)
        else:
            self.barcode_nomenclature_id = False

    @api.onchange('is_posbox')
    def _onchange_is_posbox(self):
        if not self.is_posbox:
            self.proxy_ip = False
            self.iface_scan_via_proxy = False
            self.iface_electronic_scale = False
            self.iface_cashdrawer = False
            self.iface_print_via_proxy = False
            self.iface_customer_facing_display = False

    @api.onchange('tax_regime')
    def _onchange_tax_regime(self):
        if not self.tax_regime:
            self.default_fiscal_position_id = False

    @api.onchange('tax_regime_selection')
    def _onchange_tax_regime_selection(self):
        if not self.tax_regime_selection:
            self.fiscal_position_ids = [(5, 0, 0)]

    @api.onchange('start_category')
    def _onchange_start_category(self):
        if not self.start_category:
            self.iface_start_categ_id = False

    @api.onchange('is_header_or_footer')
    def _onchange_header_footer(self):
        if not self.is_header_or_footer:
            self.receipt_header = False
            self.receipt_footer = False

    @api.multi
    def name_get(self):
        result = []
        for config in self:
            last_session = self.env['pos.session'].search(
                [('config_id', '=', config.id)], limit=1)
            if (not last_session) or (last_session.state == 'closed'):
                result.append(
                    (config.id, config.name + ' (' + _('not used') + ')'))
                continue
            result.append(
                (config.id,
                 config.name + ' (' + last_session.user_id.name + ')'))
        return result

    @api.model
    def create(self, values):
        if values.get('is_posbox') and values.get(
                'iface_customer_facing_display'):
            if values.get('customer_facing_display_html') and not values[
                    'customer_facing_display_html'].strip():
                values[
                    'customer_facing_display_html'] = self._compute_default_customer_html(
                    )
        IrSequence = self.env['ir.sequence'].sudo()
        val = {
            'name': _('POS Order %s') % values['name'],
            'padding': 4,
            'prefix': "%s/" % values['name'],
            'code': "pos.order",
            'company_id': values.get('company_id', False),
        }
        # force sequence_id field to new pos.order sequence
        values['sequence_id'] = IrSequence.create(val).id

        val.update(name=_('POS order line %s') % values['name'],
                   code='pos.order.line')
        values['sequence_line_id'] = IrSequence.create(val).id
        pos_config = super(PosConfig, self).create(values)
        pos_config.sudo()._check_modules_to_install()
        pos_config.sudo()._check_groups_implied()
        # If you plan to add something after this, use a new environment. The one above is no longer valid after the modules install.
        return pos_config

    @api.multi
    def write(self, vals):
        result = super(PosConfig, self).write(vals)

        config_display = self.filtered(
            lambda c: c.is_posbox and c.iface_customer_facing_display and not (
                c.customer_facing_display_html or '').strip())
        if config_display:
            super(PosConfig, config_display).write({
                'customer_facing_display_html':
                self._compute_default_customer_html()
            })

        self.sudo()._set_fiscal_position()
        self.sudo()._check_modules_to_install()
        self.sudo()._check_groups_implied()
        return result

    @api.multi
    def unlink(self):
        for pos_config in self.filtered(
                lambda pos_config: pos_config.sequence_id or pos_config.
                sequence_line_id):
            pos_config.sequence_id.unlink()
            pos_config.sequence_line_id.unlink()
        return super(PosConfig, self).unlink()

    def _set_fiscal_position(self):
        for config in self:
            if config.tax_regime and config.default_fiscal_position_id.id not in config.fiscal_position_ids.ids:
                config.fiscal_position_ids = [
                    (4, config.default_fiscal_position_id.id)
                ]
            elif not config.tax_regime_selection and not config.tax_regime and config.fiscal_position_ids.ids:
                config.fiscal_position_ids = [(5, 0, 0)]

    def _check_modules_to_install(self):
        module_installed = False
        for pos_config in self:
            for field_name in [
                    f for f in pos_config.fields_get_keys()
                    if f.startswith('module_')
            ]:
                module_name = field_name.split('module_')[1]
                module_to_install = self.env['ir.module.module'].sudo().search(
                    [('name', '=', module_name)])
                if getattr(pos_config,
                           field_name) and module_to_install.state not in (
                               'installed', 'to install', 'to upgrade'):
                    module_to_install.button_immediate_install()
                    module_installed = True
        # just in case we want to do something if we install a module. (like a refresh ...)
        return module_installed

    def _check_groups_implied(self):
        for pos_config in self:
            for field_name in [
                    f for f in pos_config.fields_get_keys()
                    if f.startswith('group_')
            ]:
                field = pos_config._fields[field_name]
                if field.type in ('boolean', 'selection') and hasattr(
                        field, 'implied_group'):
                    field_group_xmlids = getattr(field, 'group',
                                                 'base.group_user').split(',')
                    field_groups = self.env['res.groups'].concat(
                        *(self.env.ref(it) for it in field_group_xmlids))
                    field_groups.write({
                        'implied_ids':
                        [(4, self.env.ref(field.implied_group).id)]
                    })

    def execute(self):
        return {
            'type': 'ir.actions.client',
            'tag': 'reload',
            'params': {
                'wait': True
            }
        }

    # Methods to open the POS
    @api.multi
    def open_ui(self):
        """ open the pos interface """
        self.ensure_one()
        return {
            'type': 'ir.actions.act_url',
            'url': '/pos/web/',
            'target': 'self',
        }

    @api.multi
    def open_session_cb(self):
        """ new session button

        create one if none exist
        access cash control interface if enabled or start a session
        """
        self.ensure_one()
        if not self.current_session_id:
            self.current_session_id = self.env['pos.session'].create({
                'user_id':
                self.env.uid,
                'config_id':
                self.id
            })
            if self.current_session_id.state == 'opened':
                return self.open_ui()
            return self._open_session(self.current_session_id.id)
        return self._open_session(self.current_session_id.id)

    @api.multi
    def open_existing_session_cb(self):
        """ close session button

        access session form to validate entries
        """
        self.ensure_one()
        return self._open_session(self.current_session_id.id)

    def _open_session(self, session_id):
        return {
            'name': _('Session'),
            'view_type': 'form',
            'view_mode': 'form,tree',
            'res_model': 'pos.session',
            'res_id': session_id,
            'view_id': False,
            'type': 'ir.actions.act_window',
        }
コード例 #25
0
ファイル: purchase_report.py プロジェクト: mausvt/flectra
class PurchaseReport(models.Model):
    _name = "purchase.report"
    _description = "Purchase Report"
    _auto = False
    _order = 'date_order desc, price_total desc'

    date_order = fields.Datetime(
        'Order Date',
        readonly=True,
        help=
        "Depicts the date when the Quotation should be validated and converted into a purchase order."
    )
    state = fields.Selection([('draft', 'Draft RFQ'), ('sent', 'RFQ Sent'),
                              ('to approve', 'To Approve'),
                              ('purchase', 'Purchase Order'), ('done', 'Done'),
                              ('cancel', 'Cancelled')],
                             'Status',
                             readonly=True)
    product_id = fields.Many2one('product.product', 'Product', readonly=True)
    partner_id = fields.Many2one('res.partner', 'Vendor', readonly=True)
    date_approve = fields.Datetime('Confirmation Date', readonly=True)
    product_uom = fields.Many2one('uom.uom',
                                  'Reference Unit of Measure',
                                  required=True)
    company_id = fields.Many2one('res.company', 'Company', readonly=True)
    currency_id = fields.Many2one('res.currency', 'Currency', readonly=True)
    user_id = fields.Many2one('res.users',
                              'Purchase Representative',
                              readonly=True)
    delay = fields.Float(
        'Days to Confirm',
        digits=(16, 2),
        readonly=True,
        group_operator='avg',
        help="Amount of time between purchase approval and order by date.")
    delay_pass = fields.Float(
        'Days to Receive',
        digits=(16, 2),
        readonly=True,
        group_operator='avg',
        help=
        "Amount of time between date planned and order by date for each purchase order line."
    )
    avg_days_to_purchase = fields.Float(
        'Average Days to Purchase',
        digits=(16, 2),
        readonly=True,
        store=
        False,  # needs store=False to prevent showing up as a 'measure' option
        help=
        "Amount of time between purchase approval and document creation date. Due to a hack needed to calculate this, \
              every record will show the same average value, therefore only use this as an aggregated value with group_operator=avg"
    )
    price_total = fields.Float('Total', readonly=True)
    price_average = fields.Float('Average Cost',
                                 readonly=True,
                                 group_operator="avg")
    nbr_lines = fields.Integer('# of Lines', readonly=True)
    category_id = fields.Many2one('product.category',
                                  'Product Category',
                                  readonly=True)
    product_tmpl_id = fields.Many2one('product.template',
                                      'Product Template',
                                      readonly=True)
    country_id = fields.Many2one('res.country',
                                 'Partner Country',
                                 readonly=True)
    fiscal_position_id = fields.Many2one('account.fiscal.position',
                                         string='Fiscal Position',
                                         readonly=True)
    account_analytic_id = fields.Many2one('account.analytic.account',
                                          'Analytic Account',
                                          readonly=True)
    commercial_partner_id = fields.Many2one('res.partner',
                                            'Commercial Entity',
                                            readonly=True)
    weight = fields.Float('Gross Weight', readonly=True)
    volume = fields.Float('Volume', readonly=True)
    order_id = fields.Many2one('purchase.order', 'Order', readonly=True)
    untaxed_total = fields.Float('Untaxed Total', readonly=True)
    qty_ordered = fields.Float('Qty Ordered', readonly=True)
    qty_received = fields.Float('Qty Received', readonly=True)
    qty_billed = fields.Float('Qty Billed', readonly=True)
    qty_to_be_billed = fields.Float('Qty to be Billed', readonly=True)

    @property
    def _table_query(self):
        ''' Report needs to be dynamic to take into account multi-company selected + multi-currency rates '''
        return '%s %s %s' % (self._select(), self._from(), self._group_by())

    def _select(self):
        select_str = """
            WITH currency_rate as (%s)
                SELECT
                    po.id as order_id,
                    min(l.id) as id,
                    po.date_order as date_order,
                    po.state,
                    po.date_approve,
                    po.dest_address_id,
                    po.partner_id as partner_id,
                    po.user_id as user_id,
                    po.company_id as company_id,
                    po.fiscal_position_id as fiscal_position_id,
                    l.product_id,
                    p.product_tmpl_id,
                    t.categ_id as category_id,
                    po.currency_id,
                    t.uom_id as product_uom,
                    extract(epoch from age(po.date_approve,po.date_order))/(24*60*60)::decimal(16,2) as delay,
                    extract(epoch from age(l.date_planned,po.date_order))/(24*60*60)::decimal(16,2) as delay_pass,
                    count(*) as nbr_lines,
                    sum(l.price_total / COALESCE(po.currency_rate, 1.0))::decimal(16,2) * currency_table.rate as price_total,
                    (sum(l.product_qty * l.price_unit / COALESCE(po.currency_rate, 1.0))/NULLIF(sum(l.product_qty/line_uom.factor*product_uom.factor),0.0))::decimal(16,2) * currency_table.rate as price_average,
                    partner.country_id as country_id,
                    partner.commercial_partner_id as commercial_partner_id,
                    analytic_account.id as account_analytic_id,
                    sum(p.weight * l.product_qty/line_uom.factor*product_uom.factor) as weight,
                    sum(p.volume * l.product_qty/line_uom.factor*product_uom.factor) as volume,
                    sum(l.price_subtotal / COALESCE(po.currency_rate, 1.0))::decimal(16,2) * currency_table.rate as untaxed_total,
                    sum(l.product_qty / line_uom.factor * product_uom.factor) as qty_ordered,
                    sum(l.qty_received / line_uom.factor * product_uom.factor) as qty_received,
                    sum(l.qty_invoiced / line_uom.factor * product_uom.factor) as qty_billed,
                    case when t.purchase_method = 'purchase' 
                         then sum(l.product_qty / line_uom.factor * product_uom.factor) - sum(l.qty_invoiced / line_uom.factor * product_uom.factor)
                         else sum(l.qty_received / line_uom.factor * product_uom.factor) - sum(l.qty_invoiced / line_uom.factor * product_uom.factor)
                    end as qty_to_be_billed
        """ % self.env['res.currency']._select_companies_rates()
        return select_str

    def _from(self):
        from_str = """
            FROM
            purchase_order_line l
                join purchase_order po on (l.order_id=po.id)
                join res_partner partner on po.partner_id = partner.id
                    left join product_product p on (l.product_id=p.id)
                        left join product_template t on (p.product_tmpl_id=t.id)
                left join uom_uom line_uom on (line_uom.id=l.product_uom)
                left join uom_uom product_uom on (product_uom.id=t.uom_id)
                left join account_analytic_account analytic_account on (l.account_analytic_id = analytic_account.id)
                left join currency_rate cr on (cr.currency_id = po.currency_id and
                    cr.company_id = po.company_id and
                    cr.date_start <= coalesce(po.date_order, now()) and
                    (cr.date_end is null or cr.date_end > coalesce(po.date_order, now())))
                left join {currency_table} ON currency_table.company_id = po.company_id
        """.format(
            currency_table=self.env['res.currency']._get_query_currency_table({
                'multi_company':
                True,
                'date': {
                    'date_to': fields.Date.today()
                }
            }), )
        return from_str

    def _group_by(self):
        group_by_str = """
            GROUP BY
                po.company_id,
                po.user_id,
                po.partner_id,
                line_uom.factor,
                po.currency_id,
                l.price_unit,
                po.date_approve,
                l.date_planned,
                l.product_uom,
                po.dest_address_id,
                po.fiscal_position_id,
                l.product_id,
                p.product_tmpl_id,
                t.categ_id,
                po.date_order,
                po.state,
                line_uom.uom_type,
                line_uom.category_id,
                t.uom_id,
                t.purchase_method,
                line_uom.id,
                product_uom.factor,
                partner.country_id,
                partner.commercial_partner_id,
                analytic_account.id,
                po.id,
                currency_table.rate
        """
        return group_by_str

    @api.model
    def read_group(self,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   orderby=False,
                   lazy=True):
        """ This is a hack to allow us to correctly calculate the average of PO specific date values since
            the normal report query result will duplicate PO values across its PO lines during joins and
            lead to incorrect aggregation values.

            Only the AVG operator is supported for avg_days_to_purchase.
        """
        avg_days_to_purchase = next(
            (field for field in fields
             if re.search(r'\bavg_days_to_purchase\b', field)), False)

        if avg_days_to_purchase:
            fields.remove(avg_days_to_purchase)
            if any(
                    field.split(':')[1].split('(')[0] != 'avg'
                    for field in [avg_days_to_purchase] if field):
                raise UserError(
                    "Value: 'avg_days_to_purchase' should only be used to show an average. If you are seeing this message then it is being accessed incorrectly."
                )

        res = []
        if fields:
            res = super(PurchaseReport, self).read_group(domain,
                                                         fields,
                                                         groupby,
                                                         offset=offset,
                                                         limit=limit,
                                                         orderby=orderby,
                                                         lazy=lazy)

        if not res and avg_days_to_purchase:
            res = [{}]

        if avg_days_to_purchase:
            self.check_access_rights('read')
            query = """ SELECT AVG(days_to_purchase.po_days_to_purchase)::decimal(16,2) AS avg_days_to_purchase
                          FROM (
                              SELECT extract(epoch from age(po.date_approve,po.create_date))/(24*60*60) AS po_days_to_purchase
                              FROM purchase_order po
                              WHERE po.id IN (
                                  SELECT "purchase_report"."order_id" FROM %s WHERE %s)
                              ) AS days_to_purchase
                    """

            subdomain = AND([
                domain,
                [('company_id', '=', self.env.company.id),
                 ('date_approve', '!=', False)]
            ])
            subtables, subwhere, subparams = expression(subdomain,
                                                        self).query.get_sql()

            self.env.cr.execute(query % (subtables, subwhere), subparams)
            res[0].update({
                '__count':
                1,
                avg_days_to_purchase.split(':')[0]:
                self.env.cr.fetchall()[0][0],
            })
        return res
コード例 #26
0
class OpAssignmentSubLine(models.Model):
    _name = 'op.assignment.sub.line'
    _inherit = 'mail.thread'
    _rec_name = 'assignment_id'
    _description = 'Assignment Submission'

    assignment_id = fields.Many2one('op.assignment',
                                    'Assignment',
                                    required=True)
    student_id = fields.Many2one('op.student',
                                 'Student',
                                 default=lambda self: self.env['op.student'].
                                 search([('user_id', '=', self.env.uid)]),
                                 required=True)
    description = fields.Text('Description', track_visibility='onchange')
    state = fields.Selection([('draft', 'Draft'), ('submit', 'Submitted'),
                              ('reject', 'Rejected'),
                              ('change', 'Change Req.'),
                              ('accept', 'Accepted')],
                             'State',
                             default='draft',
                             track_visibility='onchange')
    submission_date = fields.Datetime(
        'Submission Date',
        readonly=True,
        default=lambda self: fields.Datetime.now(),
        required=True)
    marks = fields.Float('Marks', track_visibility='onchange')
    note = fields.Text('Note')
    user_id = fields.Many2one('res.users',
                              related='student_id.user_id',
                              string='User')
    faculty_user_id = fields.Many2one(
        'res.users',
        related='assignment_id.faculty_id.user_id',
        string='Faculty User')

    @api.multi
    def act_draft(self):
        result = self.state = 'draft'
        return result and result or False

    @api.multi
    def act_submit(self):
        result = self.state = 'submit'
        return result and result or False

    @api.multi
    def act_accept(self):
        result = self.state = 'accept'
        return result and result or False

    @api.multi
    def act_change_req(self):
        result = self.state = 'change'
        return result and result or False

    @api.multi
    def act_reject(self):
        result = self.state = 'reject'
        return result and result or False

    @api.multi
    def unlink(self):
        for record in self:
            if not record.state == 'draft' and not self.env.user.has_group(
                    'openeducat_core.group_op_faculty'):
                raise ValidationError(
                    _("You can't delete none draft submissions!"))
        res = super(OpAssignmentSubLine, self).unlink()
        return res

    @api.model
    def create(self, vals):
        if self.env.user.child_ids:
            raise Warning(
                _('Invalid Action!\n Parent can not \
            create Assignment Submissions!'))
        return super(OpAssignmentSubLine, self).create(vals)

    @api.multi
    def write(self, vals):
        if self.env.user.child_ids:
            raise Warning(
                _('Invalid Action!\n Parent can not edit \
            Assignment Submissions!'))
        return super(OpAssignmentSubLine, self).write(vals)
コード例 #27
0
ファイル: res_users.py プロジェクト: yemanadep/Flectra
class Groups(models.Model):
    _name = "res.groups"
    _description = "Access Groups"
    _rec_name = 'full_name'
    _order = 'name'

    name = fields.Char(required=True, translate=True)
    users = fields.Many2many('res.users', 'res_groups_users_rel', 'gid', 'uid')
    model_access = fields.One2many('ir.model.access',
                                   'group_id',
                                   string='Access Controls',
                                   copy=True)
    rule_groups = fields.Many2many('ir.rule',
                                   'rule_group_rel',
                                   'group_id',
                                   'rule_group_id',
                                   string='Rules',
                                   domain=[('global', '=', False)])
    menu_access = fields.Many2many('ir.ui.menu',
                                   'ir_ui_menu_group_rel',
                                   'gid',
                                   'menu_id',
                                   string='Access Menu')
    view_access = fields.Many2many('ir.ui.view',
                                   'ir_ui_view_group_rel',
                                   'group_id',
                                   'view_id',
                                   string='Views')
    comment = fields.Text(translate=True)
    category_id = fields.Many2one('ir.module.category',
                                  string='Application',
                                  index=True)
    color = fields.Integer(string='Color Index')
    full_name = fields.Char(compute='_compute_full_name',
                            string='Group Name',
                            search='_search_full_name')
    share = fields.Boolean(
        string='Share Group',
        help=
        "Group created to set access rights for sharing data with some users.")
    is_portal = fields.Boolean(
        'Portal', help="If checked, this group is usable as a portal.")

    _sql_constraints = [
        ('name_uniq', 'unique (category_id, name)',
         'The name of the group must be unique within an application!')
    ]

    @api.depends('category_id.name', 'name')
    def _compute_full_name(self):
        # Important: value must be stored in environment of group, not group1!
        for group, group1 in pycompat.izip(self, self.sudo()):
            if group1.category_id:
                group.full_name = '%s / %s' % (group1.category_id.name,
                                               group1.name)
            else:
                group.full_name = group1.name

    def _search_full_name(self, operator, operand):
        lst = True
        if isinstance(operand, bool):
            domains = [[('name', operator, operand)],
                       [('category_id.name', operator, operand)]]
            if operator in expression.NEGATIVE_TERM_OPERATORS == (not operand):
                return expression.AND(domains)
            else:
                return expression.OR(domains)
        if isinstance(operand, pycompat.string_types):
            lst = False
            operand = [operand]
        where = []
        for group in operand:
            values = [v for v in group.split('/') if v]
            group_name = values.pop().strip()
            category_name = values and '/'.join(values).strip() or group_name
            group_domain = [('name', operator, lst and [group_name]
                             or group_name)]
            category_domain = [('category_id.name', operator,
                                lst and [category_name] or category_name)]
            if operator in expression.NEGATIVE_TERM_OPERATORS and not values:
                category_domain = expression.OR(
                    [category_domain, [('category_id', '=', False)]])
            if (operator
                    in expression.NEGATIVE_TERM_OPERATORS) == (not values):
                sub_where = expression.AND([group_domain, category_domain])
            else:
                sub_where = expression.OR([group_domain, category_domain])
            if operator in expression.NEGATIVE_TERM_OPERATORS:
                where = expression.AND([where, sub_where])
            else:
                where = expression.OR([where, sub_where])
        return where

    @api.model
    def search(self, args, offset=0, limit=None, order=None, count=False):
        # add explicit ordering if search is sorted on full_name
        if order and order.startswith('full_name'):
            groups = super(Groups, self).search(args)
            groups = groups.sorted('full_name', reverse=order.endswith('DESC'))
            groups = groups[offset:offset +
                            limit] if limit else groups[offset:]
            return len(groups) if count else groups.ids
        return super(Groups, self).search(args,
                                          offset=offset,
                                          limit=limit,
                                          order=order,
                                          count=count)

    @api.multi
    def copy(self, default=None):
        self.ensure_one()
        chosen_name = default.get('name') if default else ''
        default_name = chosen_name or _('%s (copy)') % self.name
        default = dict(default or {}, name=default_name)
        return super(Groups, self).copy(default)

    @api.multi
    def write(self, vals):
        if 'name' in vals:
            if vals['name'].startswith('-'):
                raise UserError(
                    _('The name of the group can not start with "-"'))
        # invalidate caches before updating groups, since the recomputation of
        # field 'share' depends on method has_group()
        self.env['ir.model.access'].call_cache_clearing_methods()
        self.env['res.users'].has_group.clear_cache(self.env['res.users'])
        return super(Groups, self).write(vals)
コード例 #28
0
ファイル: stock_location.py プロジェクト: yemanadep/Flectra
class Location(models.Model):
    _name = "stock.location"
    _description = "Inventory Locations"
    _parent_name = "location_id"
    _parent_store = True
    _parent_order = 'name'
    _order = 'parent_left'
    _rec_name = 'complete_name'

    @api.model
    def default_get(self, fields):
        res = super(Location, self).default_get(fields)
        if 'barcode' in fields and 'barcode' not in res and res.get('complete_name'):
            res['barcode'] = res['complete_name']
        return res

    def _should_be_valued(self):
        self.ensure_one()
        if self.usage == 'internal' or (
                        self.usage == 'transit' and self.company_id):
            return True
        return False

    name = fields.Char('Location Name', required=True, translate=True)
    complete_name = fields.Char("Full Location Name", compute='_compute_complete_name', store=True)
    active = fields.Boolean('Active', default=True, help="By unchecking the active field, you may hide a location without deleting it.")
    usage = fields.Selection([
        ('supplier', 'Vendor Location'),
        ('view', 'View'),
        ('internal', 'Internal Location'),
        ('customer', 'Customer Location'),
        ('inventory', 'Inventory Loss'),
        ('procurement', 'Procurement'),
        ('production', 'Production'),
        ('transit', 'Transit Location')], string='Location Type',
        default='internal', index=True, required=True,
        help="* Vendor Location: Virtual location representing the source location for products coming from your vendors"
             "\n* View: Virtual location used to create a hierarchical structures for your warehouse, aggregating its child locations ; can't directly contain products"
             "\n* Internal Location: Physical locations inside your own warehouses,"
             "\n* Customer Location: Virtual location representing the destination location for products sent to your customers"
             "\n* Inventory Loss: Virtual location serving as counterpart for inventory operations used to correct stock levels (Physical inventories)"
             "\n* Procurement: Virtual location serving as temporary counterpart for procurement operations when the source (vendor or production) is not known yet. This location should be empty when the procurement scheduler has finished running."
             "\n* Production: Virtual counterpart location for production operations: this location consumes the raw material and produces finished products"
             "\n* Transit Location: Counterpart location that should be used in inter-company or inter-warehouses operations")
    location_id = fields.Many2one(
        'stock.location', 'Parent Location', index=True, ondelete='cascade',
        help="The parent location that includes this location. Example : The 'Dispatch Zone' is the 'Gate 1' parent location.")
    child_ids = fields.One2many('stock.location', 'location_id', 'Contains')
    partner_id = fields.Many2one('res.partner', 'Owner', help="Owner of the location if not internal")
    comment = fields.Text('Additional Information')
    posx = fields.Integer('Corridor (X)', default=0, help="Optional localization details, for information purpose only")
    posy = fields.Integer('Shelves (Y)', default=0, help="Optional localization details, for information purpose only")
    posz = fields.Integer('Height (Z)', default=0, help="Optional localization details, for information purpose only")
    parent_left = fields.Integer('Left Parent', index=True)
    parent_right = fields.Integer('Right Parent', index=True)
    company_id = fields.Many2one(
        'res.company', 'Company',
        default=lambda self: self.env['res.company']._company_default_get('stock.location'), index=True,
        help='Let this field empty if this location is shared between companies')
    scrap_location = fields.Boolean('Is a Scrap Location?', default=False, help='Check this box to allow using this location to put scrapped/damaged goods.')
    return_location = fields.Boolean('Is a Return Location?', help='Check this box to allow using this location as a return location.')
    removal_strategy_id = fields.Many2one('product.removal', 'Removal Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to take the products from, which lot etc. for this location. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here.")
    putaway_strategy_id = fields.Many2one('product.putaway', 'Put Away Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to store the products. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here.")
    barcode = fields.Char('Barcode', copy=False, oldname='loc_barcode')
    branch_id = fields.Many2one('res.branch', 'Branch', ondelete="restrict")
    quant_ids = fields.One2many('stock.quant', 'location_id')

    _sql_constraints = [('barcode_company_uniq', 'unique (barcode,company_id)', 'The barcode for a location must be unique per company !')]

    @api.one
    @api.depends('name', 'location_id.complete_name')
    def _compute_complete_name(self):
        """ Forms complete name of location from parent location to child location. """
        if self.location_id.complete_name:
            self.complete_name = '%s/%s' % (self.location_id.complete_name, self.name)
        else:
            self.complete_name = self.name

    def write(self, values):
        if 'usage' in values and values['usage'] == 'view':
            if self.mapped('quant_ids'):
                raise UserError(_("This location's usage cannot be changed to view as it contains products."))
        if 'usage' in values or 'scrap_location' in values:

            modified_locations = self.filtered(
                lambda l: any(l[f] != values[f] if f in values else False
                              for f in {'usage', 'scrap_location'}))
            reserved_quantities = self.env['stock.move.line'].search_count([
                ('location_id', 'in', modified_locations.ids),
                ('product_qty', '>', 0),
            ])
            if reserved_quantities:
                raise UserError(_(
                    "You cannot change the location type or its use as a scrap"
                    " location as there are products reserved in this location."
                    " Please unreserve the products first."
                ))
        return super(Location, self).write(values)

    @api.multi
    @api.constrains('branch_id', 'location_id')
    def _check_parent_branch(self):
        for record in self:
            if (
                record.location_id and
                record.location_id.usage == 'internal' and
                record.branch_id and record.branch_id != record.location_id.branch_id
            ):
                raise UserError(
                    _('Configuration Error of Branch:\n'
                      'The Location Branch (%s) and '
                      'the Branch (%s) of Parent Location must '
                      'be the same branch!') % (recordord.branch_id.name,
                                                recordord.location_id.branch_id.name)
                )

    @api.multi
    @api.constrains('branch_id')
    def _check_warehouse_branch(self):
        for record in self:
            warehouse_obj = self.env['stock.warehouse']
            warehouses_ids = warehouse_obj.search(
                ['|', '|', ('wh_input_stock_loc_id', '=', record.ids[0]),
                 ('lot_stock_id', 'in', record.ids),
                 ('wh_output_stock_loc_id', 'in', record.ids)])
            for warehouse_id in warehouses_ids:
                if record.branch_id and warehouse_id.branch_id and record.branch_id != warehouse_id.branch_id:
                    raise ValidationError(
                        _('Configuration Error of Branch:\n'
                          'The Location Branch (%s) and '
                          'the Branch (%s) of Warehouse must '
                          'be the same branch!') % (record.branch_id.name,
                                                    warehouse_id.branch_id.name)
                    )
            if record.usage != 'internal' and record.branch_id:
                raise UserError(
                    _('Configuration error of Branch:\n'
                      'The branch (%s) should be assigned to internal locations'
                      ) % (record.branch_id.name))

    @api.multi
    @api.constrains('company_id', 'branch_id')
    def _check_company_branch(self):
        for record in self:
            if record.branch_id and record.company_id != record.branch_id.company_id:
                raise UserError(
                    _('Configuration Error of Company:\n'
                      'The Company (%s) in the Stock Location and '
                      'the Company (%s) of Branch must '
                      'be the same company!') % (record.company_id.name,
                                                record.branch_id.company_id.name)
                    )

    def name_get(self):
        ret_list = []
        for location in self:
            orig_location = location
            name = location.name
            while location.location_id and location.usage != 'view':
                location = location.location_id
                if not name:
                    raise UserError(_('You have to set a name for this location.'))
                name = location.name + "/" + name
            ret_list.append((orig_location.id, name))
        return ret_list

    @api.model
    def name_search(self, name, args=None, operator='ilike', limit=100):
        """ search full name and barcode """
        if args is None:
            args = []
        recs = self.search(['|', ('barcode', operator, name), ('complete_name', operator, name)] + args, limit=limit)
        return recs.name_get()

    def get_putaway_strategy(self, product):
        ''' Returns the location where the product has to be put, if any compliant putaway strategy is found. Otherwise returns None.'''
        current_location = self
        putaway_location = self.env['stock.location']
        while current_location and not putaway_location:
            if current_location.putaway_strategy_id:
                putaway_location = current_location.putaway_strategy_id.putaway_apply(product)
            current_location = current_location.location_id
        return putaway_location

    @api.returns('stock.warehouse', lambda value: value.id)
    def get_warehouse(self):
        """ Returns warehouse id of warehouse that contains location """
        return self.env['stock.warehouse'].search([
            ('view_location_id.parent_left', '<=', self.parent_left),
            ('view_location_id.parent_right', '>=', self.parent_left)], limit=1)

    def should_bypass_reservation(self):
        self.ensure_one()
        return self.usage in ('supplier', 'customer', 'inventory', 'production') or self.scrap_location
コード例 #29
0
class AccountInvoiceRelate(models.Model):
    _inherit = 'account.invoice'

    insurance_id = fields.Many2one('insurance.details', string='Insurance')
    claim_id = fields.Many2one('claim.details', string='Insurance')
コード例 #30
0
ファイル: stock_picking.py プロジェクト: parthivgls/flectra-1
class PickingType(models.Model):
    _name = "stock.picking.type"
    _description = "The operation type determines the picking view"
    _order = 'sequence, id'

    name = fields.Char('Operation Types Name', required=True, translate=True)
    color = fields.Integer('Color')
    sequence = fields.Integer('Sequence', help="Used to order the 'All Operations' kanban view")
    sequence_id = fields.Many2one('ir.sequence', 'Reference Sequence', required=True)
    default_location_src_id = fields.Many2one(
        'stock.location', 'Default Source Location',
        help="This is the default source location when you create a picking manually with this operation type. It is possible however to change it or that the routes put another location. If it is empty, it will check for the supplier location on the partner. ")
    default_location_dest_id = fields.Many2one(
        'stock.location', 'Default Destination Location',
        help="This is the default destination location when you create a picking manually with this operation type. It is possible however to change it or that the routes put another location. If it is empty, it will check for the customer location on the partner. ")
    code = fields.Selection([('incoming', 'Vendors'), ('outgoing', 'Customers'), ('internal', 'Internal')], 'Type of Operation', required=True)
    return_picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type for Returns')
    show_entire_packs = fields.Boolean('Allow moving packs', help="If checked, this shows the packs to be moved as a whole in the Operations tab all the time, even if there was no entire pack reserved.")
    warehouse_id = fields.Many2one(
        'stock.warehouse', 'Warehouse', ondelete='cascade',
        default=lambda self: self.env['stock.warehouse'].search([('company_id', '=', self.env.user.company_id.id)], limit=1))
    active = fields.Boolean('Active', default=True)
    use_create_lots = fields.Boolean(
        'Create New Lots/Serial Numbers', default=True,
        help="If this is checked only, it will suppose you want to create new Lots/Serial Numbers, so you can provide them in a text field. ")
    use_existing_lots = fields.Boolean(
        'Use Existing Lots/Serial Numbers', default=True,
        help="If this is checked, you will be able to choose the Lots/Serial Numbers. You can also decide to not put lots in this operation type.  This means it will create stock with no lot or not put a restriction on the lot taken. ")
    show_operations = fields.Boolean(
        'Show Detailed Operations', default=False,
        help="If this checkbox is ticked, the pickings lines will represent detailed stock operations. If not, the picking lines will represent an aggregate of detailed stock operations.")
    show_reserved = fields.Boolean(
        'Show Reserved', default=True, help="If this checkbox is ticked, Flectra will show which products are reserved (lot/serial number, source location, source package).")

    # Statistics for the kanban view
    last_done_picking = fields.Char('Last 10 Done Pickings', compute='_compute_last_done_picking')
    count_picking_draft = fields.Integer(compute='_compute_picking_count')
    count_picking_ready = fields.Integer(compute='_compute_picking_count')
    count_picking = fields.Integer(compute='_compute_picking_count')
    count_picking_waiting = fields.Integer(compute='_compute_picking_count')
    count_picking_late = fields.Integer(compute='_compute_picking_count')
    count_picking_backorders = fields.Integer(compute='_compute_picking_count')
    rate_picking_late = fields.Integer(compute='_compute_picking_count')
    rate_picking_backorders = fields.Integer(compute='_compute_picking_count')

    barcode_nomenclature_id = fields.Many2one(
        'barcode.nomenclature', 'Barcode Nomenclature')

    @api.one
    def _compute_last_done_picking(self):
        # TDE TODO: true multi
        tristates = []
        for picking in self.env['stock.picking'].search([('picking_type_id', '=', self.id), ('state', '=', 'done')], order='date_done desc', limit=10):
            if picking.date_done > picking.date:
                tristates.insert(0, {'tooltip': picking.name or '' + ": " + _('Late'), 'value': -1})
            elif picking.backorder_id:
                tristates.insert(0, {'tooltip': picking.name or '' + ": " + _('Backorder exists'), 'value': 0})
            else:
                tristates.insert(0, {'tooltip': picking.name or '' + ": " + _('OK'), 'value': 1})
        self.last_done_picking = json.dumps(tristates)

    def _compute_picking_count(self):
        # TDE TODO count picking can be done using previous two
        domains = {
            'count_picking_draft': [('state', '=', 'draft')],
            'count_picking_waiting': [('state', 'in', ('confirmed', 'waiting'))],
            'count_picking_ready': [('state', '=', 'assigned')],
            'count_picking': [('state', 'in', ('assigned', 'waiting', 'confirmed'))],
            'count_picking_late': [('scheduled_date', '<', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), ('state', 'in', ('assigned', 'waiting', 'confirmed'))],
            'count_picking_backorders': [('backorder_id', '!=', False), ('state', 'in', ('confirmed', 'assigned', 'waiting'))],
        }
        for field in domains:
            data = self.env['stock.picking'].read_group(domains[field] +
                [('state', 'not in', ('done', 'cancel')), ('picking_type_id', 'in', self.ids)],
                ['picking_type_id'], ['picking_type_id'])
            count = {
                x['picking_type_id'][0]: x['picking_type_id_count']
                for x in data if x['picking_type_id']
            }
            for record in self:
                record[field] = count.get(record.id, 0)
        for record in self:
            record.rate_picking_late = record.count_picking and record.count_picking_late * 100 / record.count_picking or 0
            record.rate_picking_backorders = record.count_picking and record.count_picking_backorders * 100 / record.count_picking or 0

    def name_get(self):
        """ Display 'Warehouse_name: PickingType_name' """
        # TDE TODO remove context key support + update purchase
        res = []
        for picking_type in self:
            if self.env.context.get('special_shortened_wh_name'):
                if picking_type.warehouse_id:
                    name = picking_type.warehouse_id.name
                else:
                    name = _('Customer') + ' (' + picking_type.name + ')'
            elif picking_type.warehouse_id:
                name = picking_type.warehouse_id.name + ': ' + picking_type.name
            else:
                name = picking_type.name
            res.append((picking_type.id, name))
        return res

    @api.model
    def name_search(self, name, args=None, operator='ilike', limit=100):
        args = args or []
        domain = []
        if name:
            domain = ['|', ('name', operator, name), ('warehouse_id.name', operator, name)]
        picks = self.search(domain + args, limit=limit)
        return picks.name_get()

    @api.onchange('code')
    def onchange_picking_code(self):
        if self.code == 'incoming':
            self.default_location_src_id = self.env.ref('stock.stock_location_suppliers').id
            self.default_location_dest_id = self.env.ref('stock.stock_location_stock').id
        elif self.code == 'outgoing':
            self.default_location_src_id = self.env.ref('stock.stock_location_stock').id
            self.default_location_dest_id = self.env.ref('stock.stock_location_customers').id

    @api.onchange('show_operations')
    def onchange_show_operations(self):
        if self.show_operations is True:
            self.show_reserved = True

    def _get_action(self, action_xmlid):
        # TDE TODO check to have one view + custo in methods
        action = self.env.ref(action_xmlid).read()[0]
        if self:
            action['display_name'] = self.display_name
        return action

    def get_action_picking_tree_late(self):
        return self._get_action('stock.action_picking_tree_late')

    def get_action_picking_tree_backorder(self):
        return self._get_action('stock.action_picking_tree_backorder')

    def get_action_picking_tree_waiting(self):
        return self._get_action('stock.action_picking_tree_waiting')

    def get_action_picking_tree_ready(self):
        return self._get_action('stock.action_picking_tree_ready')

    def get_stock_picking_action_picking_type(self):
        return self._get_action('stock.stock_picking_action_picking_type')