Пример #1
0
class AcquirerbKash(models.Model):
    _inherit = 'payment.acquirer'

    provider = fields.Selection(selection_add=[('bkash', 'bKash')])
    agent_no = fields.Char("bKash Agent No")
    bkash_email_account = fields.Char('bKash Email ID',
                                      required_if_provider='bkash',
                                      groups='base.group_user')
    bkash_seller_account = fields.Char(
        'bKash Merchant ID',
        groups='base.group_user',
        help=
        'The Merchant ID is used to ensure communications coming from bKash are valid and secured.'
    )
    bkash_use_ipn = fields.Boolean('Use IPN',
                                   default=True,
                                   help='bKash Instant Payment Notification',
                                   groups='base.group_user')
    bkash_pdt_token = fields.Char(
        string='bKash PDT Token',
        help=
        'Payment Data Transfer allows you to receive notification of successful payments as they are made.',
        groups='base.group_user')
    # Server 2 server
    bkash_api_enabled = fields.Boolean('Use Rest API', default=False)
    bkash_api_username = fields.Char('Rest API Username',
                                     groups='base.group_user')
    bkash_api_password = fields.Char('Rest API Password',
                                     groups='base.group_user')
    bkash_api_access_token = fields.Char('Access Token',
                                         groups='base.group_user')
    bkash_api_access_token_validity = fields.Datetime('Access Token Validity',
                                                      groups='base.group_user')
    # Default bkash fees
    fees_dom_fixed = fields.Float(default=0.0)
    fees_dom_var = fields.Float(default=1.85)
    fees_int_fixed = fields.Float(default=0.35)
    fees_int_var = fields.Float(default=3.9)

    def _get_feature_support(self):
        """Get advanced feature support by provider.

        Each provider should add its technical in the corresponding
        key for the following features:
            * fees: support payment fees computations
            * authorize: support authorizing payment (separates
                         authorization and capture)
            * tokenize: support saving payment data in a payment.tokenize
                        object
        """
        res = super(AcquirerbKash, self)._get_feature_support()
        res['fees'].append('bkash')
        return res

    @api.model
    def _get_bkash_urls(self, environment):
        """ bKash URLS """
        if environment == 'prod':
            return {
                'bkash_form_url': '/payment/bkash/',
                'bkash_rest_url': '/payment/bkash/',
            }
        else:
            return {
                'bkash_form_url': '/payment/bkash/',
                'bkash_rest_url': '/payment/bkash/',
            }

    @api.multi
    def bkash_compute_fees(self, amount, currency_id, country_id):
        """ Compute bkash fees.

            :param float amount: the amount to pay
            :param integer country_id: an ID of a res.country, or None. This is
                                       the customer's country, to be compared to
                                       the acquirer company country.
            :return float fees: computed fees
        """
        if not self.fees_active:
            return 0.0
        country = self.env['res.country'].browse(country_id)
        if country and self.company_id.country_id.id == country.id:
            percentage = self.fees_dom_var
            fixed = self.fees_dom_fixed
        else:
            percentage = self.fees_int_var
            fixed = self.fees_int_fixed
        fees = (percentage / 100.0 * amount) + fixed / (1 - percentage / 100.0)
        return fees

    @api.multi
    def bkash_form_generate_values(self, values):
        base_url = self.env['ir.config_parameter'].sudo().get_param(
            'web.base.url')

        bkash_tx_values = dict(values)
        bkash_tx_values.update({
            'seller_account':
            self.bkash_seller_account,
            'cmd':
            '_xclick',
            'business':
            self.bkash_email_account,
            'item_name':
            '%s: %s' % (self.company_id.name, values['reference']),
            'item_number':
            values['reference'],
            'amount':
            values['amount'],
            'currency_code':
            values['currency'] and values['currency'].name or '',
            'address1':
            values.get('partner_address'),
            'city':
            values.get('partner_city'),
            'country':
            values.get('partner_country')
            and values.get('partner_country').code or '',
            'state':
            values.get('partner_state') and
            (values.get('partner_state').code
             or values.get('partner_state').name) or '',
            'email':
            values.get('partner_email'),
            'zip_code':
            values.get('partner_zip'),
            'first_name':
            values.get('partner_first_name'),
            'last_name':
            values.get('partner_last_name'),
            'bkash_return':
            urls.url_join(base_url, bKashController._return_url),
            'notify_url':
            urls.url_join(base_url, bKashController._notify_url),
            'cancel_return':
            urls.url_join(base_url, bKashController._cancel_url),
            'handling':
            '%.2f' %
            bkash_tx_values.pop('fees', 0.0) if self.fees_active else False,
            'custom':
            json.dumps({
                'return_url': '%s' % bkash_tx_values.pop('return_url')
            }) if bkash_tx_values.get('return_url') else False,
        })
        return bkash_tx_values

    @api.multi
    def bkash_get_form_action_url(self):
        return self._get_bkash_urls(self.environment)['bkash_form_url']
Пример #2
0
class HrEmployeeBase(models.AbstractModel):
    _inherit = "hr.employee.base"

    attendance_ids = fields.One2many('hr.attendance', 'employee_id', help='list of attendances for the employee')
    last_attendance_id = fields.Many2one('hr.attendance', compute='_compute_last_attendance_id', store=True)
    last_check_in = fields.Datetime(related='last_attendance_id.check_in', store=True)
    last_check_out = fields.Datetime(related='last_attendance_id.check_out', store=True)
    attendance_state = fields.Selection(string="Attendance Status", compute='_compute_attendance_state', selection=[('checked_out', "Checked out"), ('checked_in', "Checked in")])
    hours_last_month = fields.Float(compute='_compute_hours_last_month')
    hours_today = fields.Float(compute='_compute_hours_today')
    hours_last_month_display = fields.Char(compute='_compute_hours_last_month')

    def _compute_presence_state(self):
        """
        Override to include checkin/checkout in the presence state
        Attendance has the second highest priority after login
        """
        super()._compute_presence_state()
        employees = self.filtered(lambda employee: employee.hr_presence_state != 'present')
        for employee in employees:
            if employee.attendance_state == 'checked_out' and employee.hr_presence_state == 'to_define':
                employee.hr_presence_state = 'absent'
        for employee in employees:
            if employee.attendance_state == 'checked_in':
                employee.hr_presence_state = 'present'

    def _compute_hours_last_month(self):
        for employee in self:
            now = datetime.now()
            start = now + relativedelta(months=-1, day=1)
            end = now + relativedelta(days=-1, day=1)
            attendances = self.env['hr.attendance'].search([
                ('employee_id', '=', employee.id),
                ('check_in', '>=', start),
                ('check_out', '<=', end),
            ])
            employee.hours_last_month = sum(attendances.mapped('worked_hours'))
            employee.hours_last_month_display = "%g" % employee.hours_last_month

    def _compute_hours_today(self):
        now = fields.Datetime.now()
        now_utc = pytz.utc.localize(now)
        for employee in self:
            # start of day in the employee's timezone might be the previous day in utc
            tz = pytz.timezone(employee.tz)
            now_tz = now_utc.astimezone(tz)
            start_tz = now_tz + relativedelta(hour=0, minute=0)  # day start in the employee's timezone
            start_naive = start_tz.astimezone(pytz.utc).replace(tzinfo=None)

            attendances = self.env['hr.attendance'].search([
                ('employee_id', '=', employee.id),
                ('check_in', '<=', now),
                '|', ('check_out', '>=', start_naive), ('check_out', '=', False),
            ])

            worked_hours = 0
            for attendance in attendances:
                delta = (attendance.check_out or now) - max(attendance.check_in, start_naive)
                worked_hours += delta.total_seconds() / 3600.0
            employee.hours_today = worked_hours

    @api.depends('attendance_ids')
    def _compute_last_attendance_id(self):
        for employee in self:
            employee.last_attendance_id = self.env['hr.attendance'].search([
                ('employee_id', '=', employee.id),
            ], limit=1)

    @api.depends('last_attendance_id.check_in', 'last_attendance_id.check_out', 'last_attendance_id')
    def _compute_attendance_state(self):
        for employee in self:
            att = employee.last_attendance_id.sudo()
            employee.attendance_state = att and not att.check_out and 'checked_in' or 'checked_out'

    @api.model
    def attendance_scan(self, barcode):
        """ Receive a barcode scanned from the Kiosk Mode and change the attendances of corresponding employee.
            Returns either an action or a warning.
        """
        employee = self.sudo().search([('barcode', '=', barcode)], limit=1)
        if employee:
            return employee._attendance_action('hr_attendance.hr_attendance_action_kiosk_mode')
        return {'warning': _('No employee corresponding to barcode %(barcode)s') % {'barcode': barcode}}

    def attendance_manual(self, next_action, entered_pin=None):
        self.ensure_one()
        can_check_without_pin = not self.env.user.has_group('hr_attendance.group_hr_attendance_use_pin') or (self.user_id == self.env.user and entered_pin is None)
        if can_check_without_pin or entered_pin is not None and entered_pin == self.sudo().pin:
            return self._attendance_action(next_action)
        return {'warning': _('Wrong PIN')}

    def _attendance_action(self, next_action):
        """ Changes the attendance of the employee.
            Returns an action to the check in/out message,
            next_action defines which menu the check in/out message should return to. ("My Attendances" or "Kiosk Mode")
        """
        self.ensure_one()
        employee = self.sudo()
        action_message = self.env.ref('hr_attendance.hr_attendance_action_greeting_message').read()[0]
        action_message['previous_attendance_change_date'] = employee.last_attendance_id and (employee.last_attendance_id.check_out or employee.last_attendance_id.check_in) or False
        action_message['employee_name'] = employee.name
        action_message['barcode'] = employee.barcode
        action_message['next_action'] = next_action
        action_message['hours_today'] = employee.hours_today

        if employee.user_id:
            modified_attendance = employee.with_user(employee.user_id)._attendance_action_change()
        else:
            modified_attendance = employee._attendance_action_change()
        action_message['attendance'] = modified_attendance.read()[0]
        return {'action': action_message}

    def _attendance_action_change(self):
        """ Check In/Check Out action
            Check In: create a new attendance record
            Check Out: modify check_out field of appropriate attendance record
        """
        self.ensure_one()
        action_date = fields.Datetime.now()

        if self.attendance_state != 'checked_in':
            vals = {
                'employee_id': self.id,
                'check_in': action_date,
            }
            return self.env['hr.attendance'].create(vals)
        attendance = self.env['hr.attendance'].search([('employee_id', '=', self.id), ('check_out', '=', False)], limit=1)
        if attendance:
            attendance.check_out = action_date
        else:
            raise exceptions.UserError(_('Cannot perform check out on %(empl_name)s, could not find corresponding check in. '
                'Your attendances have probably been modified manually by human resources.') % {'empl_name': self.sudo().name, })
        return attendance

    @api.model
    def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
        if 'pin' in groupby or 'pin' in self.env.context.get('group_by', '') or self.env.context.get('no_group_by'):
            raise exceptions.UserError(_('Such grouping is not allowed.'))
        return super(HrEmployeeBase, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
Пример #3
0
class HrAttendance(models.Model):
    _name = "hr.attendance"
    _description = "Attendance"
    _order = "check_in desc"

    def _default_employee(self):
        return self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)

    employee_id = fields.Many2one('hr.employee', string="Employee", default=_default_employee, required=True, ondelete='cascade', index=True)
    department_id = fields.Many2one('hr.department', string="Department", related="employee_id.department_id",
        readonly=True)
    check_in = fields.Datetime(string="Check In", default=fields.Datetime.now, required=True)
    check_out = fields.Datetime(string="Check Out")
    worked_hours = fields.Float(string='Worked Hours', compute='_compute_worked_hours', store=True, readonly=True)

    def name_get(self):
        result = []
        for attendance in self:
            if not attendance.check_out:
                result.append((attendance.id, _("%(empl_name)s from %(check_in)s") % {
                    'empl_name': attendance.employee_id.name,
                    'check_in': fields.Datetime.to_string(fields.Datetime.context_timestamp(attendance, fields.Datetime.from_string(attendance.check_in))),
                }))
            else:
                result.append((attendance.id, _("%(empl_name)s from %(check_in)s to %(check_out)s") % {
                    'empl_name': attendance.employee_id.name,
                    'check_in': fields.Datetime.to_string(fields.Datetime.context_timestamp(attendance, fields.Datetime.from_string(attendance.check_in))),
                    'check_out': fields.Datetime.to_string(fields.Datetime.context_timestamp(attendance, fields.Datetime.from_string(attendance.check_out))),
                }))
        return result

    @api.depends('check_in', 'check_out')
    def _compute_worked_hours(self):
        for attendance in self:
            if attendance.check_out:
                delta = attendance.check_out - attendance.check_in
                attendance.worked_hours = delta.total_seconds() / 3600.0
            else:
                attendance.worked_hours = False

    @api.constrains('check_in', 'check_out')
    def _check_validity_check_in_check_out(self):
        """ verifies if check_in is earlier than check_out. """
        for attendance in self:
            if attendance.check_in and attendance.check_out:
                if attendance.check_out < attendance.check_in:
                    raise exceptions.ValidationError(_('"Check Out" time cannot be earlier than "Check In" time.'))

    @api.constrains('check_in', 'check_out', 'employee_id')
    def _check_validity(self):
        """ Verifies the validity of the attendance record compared to the others from the same employee.
            For the same employee we must have :
                * maximum 1 "open" attendance record (without check_out)
                * no overlapping time slices with previous employee records
        """
        for attendance in self:
            # we take the latest attendance before our check_in time and check it doesn't overlap with ours
            last_attendance_before_check_in = self.env['hr.attendance'].search([
                ('employee_id', '=', attendance.employee_id.id),
                ('check_in', '<=', attendance.check_in),
                ('id', '!=', attendance.id),
            ], order='check_in desc', limit=1)
            if last_attendance_before_check_in and last_attendance_before_check_in.check_out and last_attendance_before_check_in.check_out > attendance.check_in:
                raise exceptions.ValidationError(_("Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s") % {
                    'empl_name': attendance.employee_id.name,
                    'datetime': fields.Datetime.to_string(fields.Datetime.context_timestamp(self, fields.Datetime.from_string(attendance.check_in))),
                })

            if not attendance.check_out:
                # if our attendance is "open" (no check_out), we verify there is no other "open" attendance
                no_check_out_attendances = self.env['hr.attendance'].search([
                    ('employee_id', '=', attendance.employee_id.id),
                    ('check_out', '=', False),
                    ('id', '!=', attendance.id),
                ], order='check_in desc', limit=1)
                if no_check_out_attendances:
                    raise exceptions.ValidationError(_("Cannot create new attendance record for %(empl_name)s, the employee hasn't checked out since %(datetime)s") % {
                        'empl_name': attendance.employee_id.name,
                        'datetime': fields.Datetime.to_string(fields.Datetime.context_timestamp(self, fields.Datetime.from_string(no_check_out_attendances.check_in))),
                    })
            else:
                # we verify that the latest attendance with check_in time before our check_out time
                # is the same as the one before our check_in time computed before, otherwise it overlaps
                last_attendance_before_check_out = self.env['hr.attendance'].search([
                    ('employee_id', '=', attendance.employee_id.id),
                    ('check_in', '<', attendance.check_out),
                    ('id', '!=', attendance.id),
                ], order='check_in desc', limit=1)
                if last_attendance_before_check_out and last_attendance_before_check_in != last_attendance_before_check_out:
                    raise exceptions.ValidationError(_("Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s") % {
                        'empl_name': attendance.employee_id.name,
                        'datetime': fields.Datetime.to_string(fields.Datetime.context_timestamp(self, fields.Datetime.from_string(last_attendance_before_check_out.check_in))),
                    })

    @api.returns('self', lambda value: value.id)
    def copy(self):
        raise exceptions.UserError(_('You cannot duplicate an attendance.'))
Пример #4
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=True,
                               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.Binary(related="country_id.image",
                                 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")
    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')
    def _compute_email_phone(self):
        results = self.env['res.partner'].search_read(
            [('id', 'in', self.partner_id.ids)],
            ['id', 'email_normalized', 'mobile'],
        )
        mapped_data = {
            result['id']: {
                'email_normalized': result['email_normalized'],
                'mobile': result['mobile']
            }
            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 _prepare_visitor_send_mail_values(self):
        if self.partner_id.email:
            return {
                'res_model': 'res.partner',
                'res_id': self.partner_id.id,
                'partner_ids': [self.partner_id.id],
            }
        return {}

    def action_send_mail(self):
        self.ensure_one()
        visitor_mail_values = self._prepare_visitor_send_mail_values()
        if not visitor_mail_values:
            raise UserError(_("There is no email linked this visitor."))
        compose_form = self.env.ref('mail.email_compose_message_wizard_form',
                                    False)
        ctx = dict(
            default_model=visitor_mail_values.get('res_model'),
            default_res_id=visitor_mail_values.get('res_id'),
            default_use_template=False,
            default_partner_ids=[(6, 0, visitor_mail_values.get('partner_ids'))
                                 ],
            default_composition_mode='comment',
            default_reply_to=self.env.user.partner_id.email,
        )
        return {
            'name': _('Compose Email'),
            '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': 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)
            ])

        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 force_create and not visitor:
            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(response, website_page, visitor_sudo)

    def _handle_website_page_visit(self, response, 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, website_track_values=None):
        """ Create a visitor and add a track to it if website_track_values is set."""
        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,
        }
        if not self.env.user._is_public():
            vals['partner_id'] = self.env.user.partner_id.id
            vals['name'] = self.env.user.partner_id.name
        if website_track_values:
            vals['website_track_ids'] = [(0, 0, website_track_values)]
        return self.sudo().create(vals)

    def _cron_archive_visitors(self):
        one_week_ago = datetime.now() - timedelta(days=7)
        visitors_to_archive = self.env['website.visitor'].sudo().search([
            ('last_connection_datetime', '<', one_week_ago)
        ])
        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 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
Пример #5
0
class ResPartner(models.Model):
    _inherit = 'res.partner'

    signup_token = fields.Char(copy=False, groups="base.group_erp_manager")
    signup_type = fields.Char(string='Signup Token Type',
                              copy=False,
                              groups="base.group_erp_manager")
    signup_expiration = fields.Datetime(copy=False,
                                        groups="base.group_erp_manager")
    signup_valid = fields.Boolean(compute='_compute_signup_valid',
                                  string='Signup Token is Valid')
    signup_url = fields.Char(compute='_compute_signup_url',
                             string='Signup URL')

    @api.depends('signup_token', 'signup_expiration')
    def _compute_signup_valid(self):
        dt = now()
        for partner, partner_sudo in zip(self, self.sudo()):
            partner.signup_valid = bool(partner_sudo.signup_token) and \
            (not partner_sudo.signup_expiration or dt <= partner_sudo.signup_expiration)

    def _compute_signup_url(self):
        """ proxy for function field towards actual implementation """
        result = self.sudo()._get_signup_url_for_action()
        for partner in self:
            if any(
                    u.has_group('base.group_user') for u in partner.user_ids
                    if u != self.env.user):
                self.env['res.users'].check_access_rights('write')
            partner.signup_url = result.get(partner.id, False)

    def _get_signup_url_for_action(self,
                                   url=None,
                                   action=None,
                                   view_type=None,
                                   menu_id=None,
                                   res_id=None,
                                   model=None):
        """ generate a signup url for the given partner ids and action, possibly overriding
            the url state components (menu_id, id, view_type) """

        res = dict.fromkeys(self.ids, False)
        for partner in self:
            base_url = partner.get_base_url()
            # when required, make sure the partner has a valid signup token
            if self.env.context.get('signup_valid') and not partner.user_ids:
                partner.sudo().signup_prepare()

            route = 'login'
            # the parameters to encode for the query
            query = dict(db=self.env.cr.dbname)
            signup_type = self.env.context.get(
                'signup_force_type_in_url',
                partner.sudo().signup_type or '')
            if signup_type:
                route = 'reset_password' if signup_type == 'reset' else signup_type

            if partner.sudo().signup_token and signup_type:
                query['token'] = partner.sudo().signup_token
            elif partner.user_ids:
                query['login'] = partner.user_ids[0].login
            else:
                continue  # no signup token, no user, thus no signup url!

            if url:
                query['redirect'] = url
            else:
                fragment = dict()
                base = '/web#'
                if action == '/mail/view':
                    base = '/mail/view?'
                elif action:
                    fragment['action'] = action
                if view_type:
                    fragment['view_type'] = view_type
                if menu_id:
                    fragment['menu_id'] = menu_id
                if model:
                    fragment['model'] = model
                if res_id:
                    fragment['res_id'] = res_id

                if fragment:
                    query['redirect'] = base + werkzeug.urls.url_encode(
                        fragment)

            url = "/web/%s?%s" % (route, werkzeug.urls.url_encode(query))
            if not self.env.context.get('relative_url'):
                url = werkzeug.urls.url_join(base_url, url)
            res[partner.id] = url

        return res

    def action_signup_prepare(self):
        return self.signup_prepare()

    def signup_get_auth_param(self):
        """ Get a signup token related to the partner if signup is enabled.
            If the partner already has a user, get the login parameter.
        """
        res = defaultdict(dict)

        allow_signup = self.env['res.users']._get_signup_invitation_scope(
        ) == 'b2c'
        for partner in self:
            if allow_signup and not partner.sudo().user_ids:
                partner = partner.sudo()
                partner.signup_prepare()
                res[partner.id]['auth_signup_token'] = partner.signup_token
            elif partner.user_ids:
                res[partner.id]['auth_login'] = partner.user_ids[0].login
        return res

    def signup_cancel(self):
        return self.write({
            'signup_token': False,
            'signup_type': False,
            'signup_expiration': False
        })

    def signup_prepare(self, signup_type="signup", expiration=False):
        """ generate a new token for the partners with the given validity, if necessary
            :param expiration: the expiration datetime of the token (string, optional)
        """
        for partner in self:
            if expiration or not partner.signup_valid:
                token = random_token()
                while self._signup_retrieve_partner(token):
                    token = random_token()
                partner.write({
                    'signup_token': token,
                    'signup_type': signup_type,
                    'signup_expiration': expiration
                })
        return True

    @api.model
    def _signup_retrieve_partner(self,
                                 token,
                                 check_validity=False,
                                 raise_exception=False):
        """ find the partner corresponding to a token, and possibly check its validity
            :param token: the token to resolve
            :param check_validity: if True, also check validity
            :param raise_exception: if True, raise exception instead of returning False
            :return: partner (browse record) or False (if raise_exception is False)
        """
        partner = self.search([('signup_token', '=', token)], limit=1)
        if not partner:
            if raise_exception:
                raise exceptions.UserError(
                    _("Signup token '%s' is not valid") % token)
            return False
        if check_validity and not partner.signup_valid:
            if raise_exception:
                raise exceptions.UserError(
                    _("Signup token '%s' is no longer valid") % token)
            return False
        return partner

    @api.model
    def signup_retrieve_info(self, token):
        """ retrieve the user info about the token
            :return: a dictionary with the user information:
                - 'db': the name of the database
                - 'token': the token, if token is valid
                - 'name': the name of the partner, if token is valid
                - 'login': the user login, if the user already exists
                - 'email': the partner email, if the user does not exist
        """
        partner = self._signup_retrieve_partner(token, raise_exception=True)
        res = {'db': self.env.cr.dbname}
        if partner.signup_valid:
            res['token'] = token
            res['name'] = partner.name
        if partner.user_ids:
            res['login'] = partner.user_ids[0].login
        else:
            res['email'] = res['login'] = partner.email or ''
        return res
Пример #6
0
class SaleReport(models.Model):
    _name = "sale.report"
    _description = "Sales Analysis Report"
    _auto = False
    _rec_name = 'date'
    _order = 'date desc'

    name = fields.Char('Order Reference', readonly=True)
    date = fields.Datetime('Order Date', readonly=True)
    confirmation_date = fields.Datetime('Confirmation Date', readonly=True)
    product_id = fields.Many2one('product.product',
                                 'Product Variant',
                                 readonly=True)
    product_uom = fields.Many2one('uom.uom', 'Unit of Measure', readonly=True)
    product_uom_qty = fields.Float('Qty Ordered', readonly=True)
    qty_delivered = fields.Float('Qty Delivered', readonly=True)
    qty_to_invoice = fields.Float('Qty To Invoice', readonly=True)
    qty_invoiced = fields.Float('Qty Invoiced', readonly=True)
    partner_id = fields.Many2one('res.partner', 'Customer', readonly=True)
    company_id = fields.Many2one('res.company', 'Company', readonly=True)
    user_id = fields.Many2one('res.users', 'Salesperson', readonly=True)
    price_total = fields.Float('Total', readonly=True)
    price_subtotal = fields.Float('Untaxed Total', readonly=True)
    untaxed_amount_to_invoice = fields.Float('Untaxed Amount To Invoice',
                                             readonly=True)
    untaxed_amount_invoiced = fields.Float('Untaxed Amount Invoiced',
                                           readonly=True)
    product_tmpl_id = fields.Many2one('product.template',
                                      'Product',
                                      readonly=True)
    categ_id = fields.Many2one('product.category',
                               'Product Category',
                               readonly=True)
    nbr = fields.Integer('# of Lines', readonly=True)
    pricelist_id = fields.Many2one('product.pricelist',
                                   'Pricelist',
                                   readonly=True)
    analytic_account_id = fields.Many2one('account.analytic.account',
                                          'Analytic Account',
                                          readonly=True)
    team_id = fields.Many2one('crm.team',
                              'Sales Team',
                              readonly=True,
                              oldname='section_id')
    country_id = fields.Many2one('res.country',
                                 'Customer Country',
                                 readonly=True)
    commercial_partner_id = fields.Many2one('res.partner',
                                            'Customer Entity',
                                            readonly=True)
    state = fields.Selection([
        ('draft', 'Draft Quotation'),
        ('sent', 'Quotation Sent'),
        ('sale', 'Sales Order'),
        ('done', 'Sales Done'),
        ('cancel', 'Cancelled'),
    ],
                             string='Status',
                             readonly=True)
    weight = fields.Float('Gross Weight', readonly=True)
    volume = fields.Float('Volume', readonly=True)

    discount = fields.Float('Discount %', readonly=True)
    discount_amount = fields.Float('Discount Amount', readonly=True)

    order_id = fields.Many2one('sale.order', 'Order #', readonly=True)

    def _query(self, with_clause='', fields={}, groupby='', from_clause=''):
        with_ = ("WITH %s" % with_clause) if with_clause else ""

        select_ = """
            min(l.id) as id,
            l.product_id as product_id,
            t.uom_id as product_uom,
            sum(l.product_uom_qty / u.factor * u2.factor) as product_uom_qty,
            sum(l.qty_delivered / u.factor * u2.factor) as qty_delivered,
            sum(l.qty_invoiced / u.factor * u2.factor) as qty_invoiced,
            sum(l.qty_to_invoice / u.factor * u2.factor) as qty_to_invoice,
            sum(l.price_total / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) as price_total,
            sum(l.price_subtotal / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) as price_subtotal,
            sum(l.untaxed_amount_to_invoice / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) as untaxed_amount_to_invoice,
            sum(l.untaxed_amount_invoiced / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) as untaxed_amount_invoiced,
            count(*) as nbr,
            s.name as name,
            s.date_order as date,
            s.confirmation_date as confirmation_date,
            s.state as state,
            s.partner_id as partner_id,
            s.user_id as user_id,
            s.company_id as company_id,
            extract(epoch from avg(date_trunc('day',s.date_order)-date_trunc('day',s.create_date)))/(24*60*60)::decimal(16,2) as delay,
            t.categ_id as categ_id,
            s.pricelist_id as pricelist_id,
            s.analytic_account_id as analytic_account_id,
            s.team_id as team_id,
            p.product_tmpl_id,
            partner.country_id as country_id,
            partner.commercial_partner_id as commercial_partner_id,
            sum(p.weight * l.product_uom_qty / u.factor * u2.factor) as weight,
            sum(p.volume * l.product_uom_qty / u.factor * u2.factor) as volume,
            l.discount as discount,
            sum((l.price_unit * l.discount / 100.0 / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END)) as discount_amount,
            s.id as order_id
        """

        for field in fields.values():
            select_ += field

        from_ = """
                sale_order_line l
                      join sale_order s on (l.order_id=s.id)
                      join res_partner partner on s.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 u on (u.id=l.product_uom)
                    left join uom_uom u2 on (u2.id=t.uom_id)
                    left join product_pricelist pp on (s.pricelist_id = pp.id)
                %s
        """ % from_clause

        groupby_ = """
            l.product_id,
            l.order_id,
            t.uom_id,
            t.categ_id,
            s.name,
            s.date_order,
            s.confirmation_date,
            s.partner_id,
            s.user_id,
            s.state,
            s.company_id,
            s.pricelist_id,
            s.analytic_account_id,
            s.team_id,
            p.product_tmpl_id,
            partner.country_id,
            partner.commercial_partner_id,
            l.discount,
            s.id %s
        """ % (groupby)

        return '%s (SELECT %s FROM %s WHERE l.product_id IS NOT NULL GROUP BY %s)' % (
            with_, select_, from_, groupby_)

    @api.model_cr
    def init(self):
        # self._table = sale_report
        tools.drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute("""CREATE or REPLACE VIEW %s as (%s)""" %
                            (self._table, self._query()))
Пример #7
0
class EagleeduApplication(models.Model):
    _name = 'eagleedu.application'
    _description = 'This is Student Application Form'
    # _order = 'id desc'
    # _inherit = ['mail.thread']

    application_no = fields.Char(string='Application No.',
                                 required=True,
                                 copy=False,
                                 readonly=True,
                                 index=True,
                                 default=lambda self: _('New'))

    application_date = fields.Datetime(
        'Application Date', default=lambda self: fields.datetime.now(
        ))  # , default=fields.Datetime.now, required=True
    name = fields.Char(string='Student Name',
                       required=True,
                       help="Enter Name of Student")
    st_name_b = fields.Char(string='Student Bangla Name')
    st_image = fields.Binary(string='Image',
                             help="Provide the image of the Student")
    st_father_name = fields.Char(string="Father's Name",
                                 help="Proud to say my father is",
                                 required=False)
    st_father_name_b = fields.Char(string="বাবার নাম",
                                   help="Proud to say my father is")
    st_father_occupation = fields.Char(string="Father's Occupation",
                                       help="father Occupation")
    st_father_email = fields.Char(string="Father's Email",
                                  help="father Occupation")
    father_mobile = fields.Char(string="Father's Mobile No",
                                help="Father's Mobile No")
    st_mother_name = fields.Char(string="Mother's Name",
                                 help="Proud to say my mother is",
                                 required=False)
    st_mother_name_b = fields.Char(string="মা এর নাম",
                                   help="Proud to say my mother is")
    st_mother_occupation = fields.Char(string="Mother Occupation",
                                       help="Proud to say my mother is")
    st_mother_email = fields.Char(string="Mother Email",
                                  help="Proud to say my mother is")
    mother_mobile = fields.Char(string="Mother's Mobile No",
                                help="mother's Mobile No")
    date_of_birth = fields.Date(string="Date Of birth", help="Enter your DOB")
    age = fields.Char(compute="get_student_age",
                      string="Age",
                      store=True,
                      help="Enter your DOB")
    st_gender = fields.Selection([('male', 'Male'), ('female', 'Female'),
                                  ('other', 'Other')],
                                 string='Gender',
                                 required=False,
                                 track_visibility='onchange')
    st_blood_group = fields.Selection([('a+', 'A+'), ('a-', 'A-'),
                                       ('b+', 'B+'), ('o+', 'O+'),
                                       ('o-', 'O-'), ('ab-', 'AB-'),
                                       ('ab+', 'AB+')],
                                      string='Blood Group',
                                      track_visibility='onchange')
    st_passport_no = fields.Char(string="Passport No.",
                                 help="Proud to say my father is",
                                 required=False)
    nationality = fields.Many2one('res.country',
                                  string='Nationality',
                                  ondelete='restrict',
                                  default=19,
                                  help="Select the Nationality")
    academic_year = fields.Many2one('eagleedu.academic.year',
                                    string='Academic Year')
    house_no = fields.Char(string='House No.', help="Enter the House No.")
    road_no = fields.Char(string='Area/Road No.',
                          help="Enter the Area or Road No.")
    post_office = fields.Char(string='Post Office',
                              help="Enter the Post Office Name")
    city = fields.Char(string='City', help="Enter the City name")
    bd_division_id = fields.Many2one('eagleedu.bddivision',
                                     string='State / Division')
    country_id = fields.Many2one('res.country',
                                 string='Country',
                                 ondelete='restrict',
                                 default=19)
    if_same_address = fields.Boolean(string="Permanent Address same as above",
                                     default=True)
    per_village = fields.Char(string='Village Name',
                              help="Enter the Village Name")
    per_po = fields.Char(string='Post Office Name',
                         help="Enter the Post office Name ")
    per_ps = fields.Char(string='Police Station',
                         help="Enter the Police Station Name")
    per_dist_id = fields.Many2one('eagleedu.bddistrict',
                                  string='District',
                                  help="Enter the City of District name")
    per_bd_division_id = fields.Many2one(
        'eagleedu.bddivision',
        string='State / Division',
        help="Enter the City of District name")
    per_country_id = fields.Many2one('res.country',
                                     string='Country',
                                     ondelete='restrict',
                                     default=19)

    guardian_name = fields.Char(string="Guardian's Name",
                                help="Proud to say my guardian is")
    guardian_relation = fields.Many2one('eagleedu.guardian.relation',
                                        string="Relation to Guardian")
    guardian_mobile = fields.Char(string="guardian's Mobile No",
                                  help="guardian's Mobile No")
    description = fields.Text(string="Note")

    religious_id = fields.Many2one('eagleedu.religious',
                                   string="Religious",
                                   help="My Religion is ")
    standard_class = fields.Many2one('eagleedu.standard_class')
    class_id = fields.Many2one('eagleedu.standard_class')

    group_division = fields.Many2one('eagleedu.group_division')

    register_id = fields.Many2one('eagleedu.register',
                                  string="Admission Register",
                                  required=False,
                                  help="Enter the admission register Name")

    academic_year_id = fields.Many2one(
        'eagleedu.academic.year',
        related='register_id.academic_year',
        string='Academic Year',
        help="Choose Academic year for which the admission is choosing")

    student_id = fields.Char('Student Id')
    roll_no = fields.Integer('Roll No')
    section = fields.Char('Section')
    state = fields.Selection([('draft', 'Draft'), ('verification', 'Verify'),
                              ('approve', 'Approve'), ('done', 'Done')],
                             string='Status',
                             required=True,
                             default='draft',
                             track_visibility='onchange')

    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=lambda self: self.env.user.company_id)
    email = fields.Char(string="Student Email",
                        help="Enter E-mail id for contact purpose")
    phone = fields.Char(string="Student Phone",
                        help="Enter Phone no. for contact purpose")
    mobile = fields.Char(string="Student Mobile",
                         help="Enter Mobile num for contact purpose")
    nationality = fields.Many2one('res.country',
                                  string='Nationality',
                                  ondelete='restrict',
                                  default=19)

    # @api.depends('application_no', 'application_no.birthday', 'application_date')
    @api.depends('date_of_birth', 'application_date')
    def get_student_age(self):
        for rec in self:
            age = ''
            if rec.date_of_birth:
                end_data = rec.application_date or fields.Datetime.now()
                delta = relativedelta(end_data, rec.date_of_birth)
                if delta.years <= 25:
                    age = str(delta.years) + _(" Years  ") + str(
                        delta.months) + _(" Month  ") + str(
                            delta.days) + _(" Days")
                else:
                    age = str(delta.years) + _(" Year")
            rec.age = age

    @api.onchange('guardian_relation')
    def guardian_relation_changed(self):
        for rec in self:
            if rec.guardian_relation.name:
                if rec.guardian_relation.name == 'Father':
                    rec.guardian_mobile = rec.father_mobile
                    rec.guardian_name = rec.st_father_name
                elif rec.guardian_relation.name == 'Mother':
                    rec.guardian_mobile = rec.mother_mobile
                    rec.guardian_name = rec.st_mother_name

    @api.model
    def create(self, vals):
        """Overriding the create method and assigning the the sequence for the record"""
        if vals.get('application_no', _('New')) == _('New'):
            vals['application_no'] = self.env['ir.sequence'].next_by_code(
                'eagleedu.application') or _('New')
        res = super(EagleeduApplication, self).create(vals)
        return res

#    @api.model

    def send_to_verify(self):
        """Return the state to done if the documents are perfect"""
        for rec in self:
            rec.write({'state': 'verification'})

#    @api.model

    def application_verify(self):
        """Return the state to done if the documents are perfect"""
        for rec in self:
            rec.write({'state': 'approve'})


#    @api.model

    def create_student(self):
        """Create student from the application and data and return the student"""
        for rec in self:
            values = {
                'name': rec.name,
                'st_name_b': rec.st_name_b,
                'st_image': rec.st_image,
                'application_no': rec.id,
                'st_father_name': rec.st_father_name,
                'st_father_name_b': rec.st_father_name_b,
                'father_mobile': rec.father_mobile,
                'st_father_occupation': rec.st_father_occupation,
                'st_mother_name': rec.st_mother_name,
                'st_mother_name_b': rec.st_mother_name_b,
                'mother_mobile': rec.mother_mobile,
                'st_mother_occupation': rec.st_mother_occupation,
                'st_gender': rec.st_gender,
                'date_of_birth': rec.date_of_birth,
                'st_blood_group': rec.st_blood_group,
                'st_passport_no': rec.st_passport_no,
                'nationality': rec.nationality.id,
                'academic_year': rec.academic_year.id,
                'standard_class': rec.standard_class.id,
                'group_division': rec.group_division.id,
                'house_no': rec.house_no,
                'road_no': rec.road_no,
                'post_office': rec.post_office,
                'city': rec.city,
                'bd_division_id': rec.bd_division_id.id,
                'country_id': rec.country_id.id,
                'per_village': rec.per_village,
                'per_po': rec.per_po,
                'per_ps': rec.per_ps,
                'per_dist_id': rec.per_dist_id.id,
                'per_bd_division_id': rec.per_bd_division_id.id,
                'per_country_id': rec.per_country_id.id,
                'guardian_name': rec.guardian_name,
                'religious_id': rec.religious_id.id,
                # 'is_student': True,
                'student_id': rec.student_id,
                'roll_no': rec.roll_no,
                'application_no': rec.application_no,
            }
            student = self.env['eagleedu.student'].create(values)
            rec.write({'state': 'done'})
            return {
                'name': _('Student'),
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'eagleedu.student',
                'type': 'ir.actions.act_window',
                'res_id': student.id,
                'context': self.env.context
            }
Пример #8
0

def inverse_fn(records):
    pass


MODELS = [
    ('boolean', fields.Boolean()),
    ('integer', fields.Integer()),
    ('float', fields.Float()),
    ('decimal', fields.Float(digits=(16, 3))),
    ('string.bounded', fields.Char(size=16)),
    ('string.required', fields.Char(size=None, required=True)),
    ('string', fields.Char(size=None)),
    ('date', fields.Date()),
    ('datetime', fields.Datetime()),
    ('text', fields.Text()),
    ('selection',
     fields.Selection([('1', "Foo"), ('2', "Bar"), ('3', "Qux"), ('4', '')])),
    ('selection.function', fields.Selection(selection_fn)),
    # just relate to an integer
    ('many2one', fields.Many2one('export.integer')),
    ('one2many', fields.One2many('export.one2many.child', 'parent_id')),
    ('many2many', fields.Many2many('export.many2many.other')),
    ('function', fields.Integer(compute=compute_fn, inverse=inverse_fn)),
    # related: specialization of fields.function, should work the same way
    # TODO: reference
]

for name, field in MODELS:
Пример #9
0
class EagleeduAcademicYear(models.Model):
    _name = 'eagleedu.academic.year'
    _description = 'Academic Year'
    _order = 'sequence asc'
    _rec_name = 'name'

    name = fields.Char(string='Year Name',
                       required=True,
                       help='Name of academic year')
    # academic_year_id = fields.Char(string='Year Name', required=True, help='Name of academic year')
    ay_code = fields.Char(string='Code',
                          compute='set_ay_code',
                          required=True,
                          help='Code of academic year')  # related='name',
    sequence = fields.Integer(string='Sequence', required=True)

    current_year = datetime.now().year

    start_date = fields.Datetime(string='Start Date',
                                 default=datetime.strptime(
                                     '%s-01-01' % (current_year), '%Y-%m-%d'))
    end_date = fields.Datetime(string='End Date',
                               default=datetime.strptime(
                                   '%s-12-31' % (current_year), '%Y-%m-%d'))

    # academic_year_start_date = fields.Date(string='Start date', required=True, help='Starting date of academic year')
    # academic_year_end_date = fields.Date(string='End date', required=True, help='Ending of academic year')

    # start_date = fields.Date(string='Start date', required=True, help='Starting date of academic year')
    # end_date = fields.Date(string='End date', required=True, help='Ending of academic year')

    # start_date = fields.Date(string='Start Date', required=True, default=datetime.date.strftime('%Y-01-01'))

    # starting_day_of_current_year = datetime.now().date().replace(month=1, day=1)
    # ending_day_of_current_year = datetime.now().date().replace(month=12, day=31)

    # start_date = fields.Date(string='Start date', required=True, default=time.strftime('%Y-01-01'))
    # end_date = fields.Date(string='End date', required=True, default=time.strftime('%Y-31-12'))

    # date_to = fields.Date(string="End Date", default=time.strftime('%Y-01-01'))

    academic_year_description = fields.Text(
        string='Description', help="Description about the academic year")
    active = fields.Boolean(
        'Active',
        default=True,
        help=
        "If unchecked, it will allow you to hide the Academic Year without removing it."
    )

    @api.model
    def create(self, vals):
        """Over riding the create method and assigning the
        sequence for the newly creating record"""
        vals['sequence'] = self.env['ir.sequence'].next_by_code(
            'eagleedu.academic.year')
        res = super(EagleeduAcademicYear, self).create(vals)
        return res

    @api.onchange('name')
    def set_ay_code(self):
        self.ay_code = self.name
Пример #10
0
class PosSession(models.Model):
    _name = 'pos.session'
    _order = 'id desc'
    _description = 'Point of Sale Session'
    _inherit = ['mail.thread', 'mail.activity.mixin']

    POS_SESSION_STATE = [
        ('new_session', 'New Session'),
        ('opening_control', 'Opening Control'),  # method action_pos_session_open
        ('opened', 'In Progress'),               # method action_pos_session_closing_control
        ('closing_control', 'Closing Control'),  # method action_pos_session_close
        ('closed', 'Closed & Posted'),
    ]

    company_id = fields.Many2one('res.company', related='config_id.company_id', string="Company", readonly=True)

    config_id = fields.Many2one(
        'pos.config', string='Point of Sale',
        help="The physical point of sale you will use.",
        required=True,
        index=True)
    name = fields.Char(string='Session ID', required=True, readonly=True, default='/')
    user_id = fields.Many2one(
        'res.users', string='Responsible',
        required=True,
        index=True,
        readonly=True,
        states={'opening_control': [('readonly', False)]},
        default=lambda self: self.env.uid,
        ondelete='restrict')
    currency_id = fields.Many2one('res.currency', related='config_id.currency_id', string="Currency", readonly=False)
    start_at = fields.Datetime(string='Opening Date', readonly=True)
    stop_at = fields.Datetime(string='Closing Date', readonly=True, copy=False)

    state = fields.Selection(
        POS_SESSION_STATE, string='Status',
        required=True, readonly=True,
        index=True, copy=False, default='new_session')

    sequence_number = fields.Integer(string='Order Sequence Number', help='A sequence number that is incremented with each order', default=1)
    login_number = fields.Integer(string='Login Sequence Number', help='A sequence number that is incremented each time a user resumes the pos session', default=0)

    cash_control = fields.Boolean(compute='_compute_cash_all', string='Has Cash Control', compute_sudo=True)
    cash_journal_id = fields.Many2one('account.journal', compute='_compute_cash_all', string='Cash Journal', store=True)
    cash_register_id = fields.Many2one('account.bank.statement', compute='_compute_cash_all', string='Cash Register', store=True)

    cash_register_balance_end_real = fields.Monetary(
        related='cash_register_id.balance_end_real',
        string="Ending Balance",
        help="Total of closing cash control lines.",
        readonly=True)
    cash_register_balance_start = fields.Monetary(
        related='cash_register_id.balance_start',
        string="Starting Balance",
        help="Total of opening cash control lines.",
        readonly=True)
    cash_register_total_entry_encoding = fields.Monetary(
        compute='_compute_cash_balance',
        string='Total Cash Transaction',
        readonly=True,
        help="Total of all paid sales orders")
    cash_register_balance_end = fields.Monetary(
        compute='_compute_cash_balance',
        string="Theoretical Closing Balance",
        help="Sum of opening balance and transactions.",
        readonly=True)
    cash_register_difference = fields.Monetary(
        compute='_compute_cash_balance',
        string='Difference',
        help="Difference between the theoretical closing balance and the real closing balance.",
        readonly=True)

    order_ids = fields.One2many('pos.order', 'session_id',  string='Orders')
    order_count = fields.Integer(compute='_compute_order_count')
    statement_ids = fields.One2many('account.bank.statement', 'pos_session_id', string='Cash Statements', readonly=True)
    picking_count = fields.Integer(compute='_compute_picking_count')
    rescue = fields.Boolean(string='Recovery Session',
        help="Auto-generated session for orphan orders, ignored in constraints",
        readonly=True,
        copy=False)
    move_id = fields.Many2one('account.move', string='Journal Entry')
    payment_method_ids = fields.Many2many('pos.payment.method', related='config_id.payment_method_ids', string='Payment Methods')
    total_payments_amount = fields.Float(compute='_compute_total_payments_amount', string='Total Payments Amount')
    is_in_company_currency = fields.Boolean('Is Using Company Currency', compute='_compute_is_in_company_currency')

    _sql_constraints = [('uniq_name', 'unique(name)', "The name of this POS Session must be unique !")]

    @api.depends('currency_id', 'company_id.currency_id')
    def _compute_is_in_company_currency(self):
        for session in self:
            session.is_in_company_currency = session.currency_id == session.company_id.currency_id

    @api.depends('payment_method_ids', 'order_ids', 'cash_register_balance_start', 'cash_register_id')
    def _compute_cash_balance(self):
        for session in self:
            cash_payment_method = session.payment_method_ids.filtered('is_cash_count')[:1]
            if cash_payment_method:
                total_cash_payment = sum(session.order_ids.mapped('payment_ids').filtered(lambda payment: payment.payment_method_id == cash_payment_method).mapped('amount'))
                session.cash_register_total_entry_encoding = session.cash_register_id.total_entry_encoding + (
                    0.0 if session.state == 'closed' else total_cash_payment
                )
                session.cash_register_balance_end = session.cash_register_balance_start + session.cash_register_total_entry_encoding
                session.cash_register_difference = session.cash_register_balance_end_real - session.cash_register_balance_end
            else:
                session.cash_register_total_entry_encoding = 0.0
                session.cash_register_balance_end = 0.0
                session.cash_register_difference = 0.0

    @api.depends('order_ids.payment_ids.amount')
    def _compute_total_payments_amount(self):
        for session in self:
            session.total_payments_amount = sum(session.order_ids.mapped('payment_ids.amount'))

    def _compute_order_count(self):
        orders_data = self.env['pos.order'].read_group([('session_id', 'in', self.ids)], ['session_id'], ['session_id'])
        sessions_data = {order_data['session_id'][0]: order_data['session_id_count'] for order_data in orders_data}
        for session in self:
            session.order_count = sessions_data.get(session.id, 0)

    def _compute_picking_count(self):
        for pos in self:
            pickings = pos.order_ids.mapped('picking_id').filtered(lambda x: x.state != 'done')
            pos.picking_count = len(pickings.ids)

    def action_stock_picking(self):
        pickings = self.order_ids.mapped('picking_id').filtered(lambda x: x.state != 'done')
        action_picking = self.env.ref('stock.action_picking_tree_ready')
        action = action_picking.read()[0]
        action['context'] = {}
        action['domain'] = [('id', 'in', pickings.ids)]
        return action

    @api.depends('config_id', 'statement_ids', 'payment_method_ids')
    def _compute_cash_all(self):
        # Only one cash register is supported by point_of_sale.
        for session in self:
            session.cash_journal_id = session.cash_register_id = session.cash_control = False
            cash_payment_methods = session.payment_method_ids.filtered('is_cash_count')
            if not cash_payment_methods:
                continue
            for statement in session.statement_ids:
                if statement.journal_id == cash_payment_methods[0].cash_journal_id:
                    session.cash_control = session.config_id.cash_control
                    session.cash_journal_id = statement.journal_id.id
                    session.cash_register_id = statement.id
                    break  # stop iteration after finding the cash journal

    @api.constrains('config_id')
    def _check_pos_config(self):
        if self.search_count([
                ('state', '!=', 'closed'),
                ('config_id', '=', self.config_id.id),
                ('rescue', '=', False)
            ]) > 1:
            raise ValidationError(_("Another session is already opened for this point of sale."))

    @api.constrains('start_at')
    def _check_start_date(self):
        for record in self:
            company = record.config_id.journal_id.company_id
            start_date = record.start_at.date()
            if (company.period_lock_date and start_date <= company.period_lock_date) or (company.fiscalyear_lock_date and start_date <= company.fiscalyear_lock_date):
                raise ValidationError(_("You cannot create a session before the accounting lock date."))

    @api.model
    def create(self, values):
        config_id = values.get('config_id') or self.env.context.get('default_config_id')
        if not config_id:
            raise UserError(_("You should assign a Point of Sale to your session."))

        # journal_id is not required on the pos_config because it does not
        # exists at the installation. If nothing is configured at the
        # installation we do the minimal configuration. Impossible to do in
        # the .xml files as the CoA is not yet installed.
        pos_config = self.env['pos.config'].browse(config_id)
        ctx = dict(self.env.context, company_id=pos_config.company_id.id)

        pos_name = self.env['ir.sequence'].with_context(ctx).next_by_code('pos.session')
        if values.get('name'):
            pos_name += ' ' + values['name']

        cash_payment_methods = pos_config.payment_method_ids.filtered(lambda pm: pm.is_cash_count)
        statement_ids = self.env['account.bank.statement']
        if self.user_has_groups('point_of_sale.group_pos_user'):
            statement_ids = statement_ids.sudo()
        for cash_journal in cash_payment_methods.mapped('cash_journal_id'):
            ctx['journal_id'] = cash_journal.id if pos_config.cash_control and cash_journal.type == 'cash' else False
            st_values = {
                'journal_id': cash_journal.id,
                'user_id': self.env.user.id,
                'name': pos_name,
                'balance_start': self.env["account.bank.statement"]._get_opening_balance(cash_journal.id) if cash_journal.type == 'cash' else 0
            }
            statement_ids |= statement_ids.with_context(ctx).create(st_values)

        values.update({
            'name': pos_name,
            'statement_ids': [(6, 0, statement_ids.ids)],
            'config_id': config_id,
        })

        if self.user_has_groups('point_of_sale.group_pos_user'):
            res = super(PosSession, self.with_context(ctx).sudo()).create(values)
        else:
            res = super(PosSession, self.with_context(ctx)).create(values)
        if not pos_config.cash_control:
            res.action_pos_session_open()

        return res

    def unlink(self):
        for session in self.filtered(lambda s: s.statement_ids):
            session.statement_ids.unlink()
        return super(PosSession, self).unlink()

    def login(self):
        self.ensure_one()
        login_number = self.login_number + 1
        self.write({
            'login_number': login_number,
        })
        return login_number

    def action_pos_session_open(self):
        # second browse because we need to refetch the data from the DB for cash_register_id
        # we only open sessions that haven't already been opened
        for session in self.filtered(lambda session: session.state in ('new_session', 'opening_control')):
            values = {}
            if not session.start_at:
                values['start_at'] = fields.Datetime.now()
            values['state'] = 'opened'
            session.write(values)
            session.statement_ids.button_open()
        return True

    def action_pos_session_closing_control(self):
        self._check_pos_session_balance()
        for session in self:
            session.write({'state': 'closing_control', 'stop_at': fields.Datetime.now()})
            if not session.config_id.cash_control:
                session.action_pos_session_close()

    def _check_pos_session_balance(self):
        for session in self:
            for statement in session.statement_ids:
                if (statement != session.cash_register_id) and (statement.balance_end != statement.balance_end_real):
                    statement.write({'balance_end_real': statement.balance_end})

    def action_pos_session_validate(self):
        self._check_pos_session_balance()
        return self.action_pos_session_close()

    def action_pos_session_close(self):
        # Session without cash payment method will not have a cash register.
        # However, there could be other payment methods, thus, session still
        # needs to be validated.
        if not self.cash_register_id:
            return self._validate_session()

        if self.cash_control and abs(self.cash_register_difference) > self.config_id.amount_authorized_diff:
            # Only pos manager can close statements with cash_register_difference greater than amount_authorized_diff.
            if not self.user_has_groups("point_of_sale.group_pos_manager"):
                raise UserError(_(
                    "Your ending balance is too different from the theoretical cash closing (%.2f), "
                    "the maximum allowed is: %.2f. You can contact your manager to force it."
                ) % (self.cash_register_difference, self.config_id.amount_authorized_diff))
            else:
                return self._warning_balance_closing()
        else:
            return self._validate_session()

    def _validate_session(self):
        self.ensure_one()
        self._check_if_no_draft_orders()
        self._create_account_move()
        if self.move_id.line_ids:
            self.move_id.post()
            # Set the uninvoiced orders' state to 'done'
            self.env['pos.order'].search([('session_id', '=', self.id), ('state', '=', 'paid')]).write({'state': 'done'})
        else:
            # The cash register needs to be confirmed for cash diffs
            # made thru cash in/out when sesion is in cash_control.
            if self.config_id.cash_control:
                self.cash_register_id.button_confirm_bank()
            self.move_id.unlink()
        self.write({'state': 'closed'})
        return {
            'type': 'ir.actions.client',
            'name': 'Point of Sale Menu',
            'tag': 'reload',
            'params': {'menu_id': self.env.ref('point_of_sale.menu_point_root').id},
        }

    def _create_account_move(self):
        """ Create account.move and account.move.line records for this session.

        Side-effects include:
            - setting self.move_id to the created account.move record
            - creating and validating account.bank.statement for cash payments
            - reconciling cash receivable lines, invoice receivable lines and stock output lines
        """
        journal = self.config_id.journal_id
        # Passing default_journal_id for the calculation of default currency of account move
        # See _get_default_currency in the account/account_move.py.
        account_move = self.env['account.move'].with_context(default_journal_id=journal.id).create({
            'journal_id': journal.id,
            'date': fields.Date.context_today(self),
            'ref': self.name,
        })
        self.write({'move_id': account_move.id})

        ## SECTION: Accumulate the amounts for each accounting lines group
        # Each dict maps `key` -> `amounts`, where `key` is the group key.
        # E.g. `combine_receivables` is derived from pos.payment records
        # in the self.order_ids with group key of the `payment_method_id`
        # field of the pos.payment record.
        amounts = lambda: {'amount': 0.0, 'amount_converted': 0.0}
        tax_amounts = lambda: {'amount': 0.0, 'amount_converted': 0.0, 'base_amount': 0.0}
        split_receivables = defaultdict(amounts)
        split_receivables_cash = defaultdict(amounts)
        combine_receivables = defaultdict(amounts)
        combine_receivables_cash = defaultdict(amounts)
        invoice_receivables = defaultdict(amounts)
        sales = defaultdict(amounts)
        taxes = defaultdict(tax_amounts)
        stock_expense = defaultdict(amounts)
        stock_output = defaultdict(amounts)
        # Track the receivable lines of the invoiced orders' account moves for reconciliation
        # These receivable lines are reconciled to the corresponding invoice receivable lines
        # of this session's move_id.
        order_account_move_receivable_lines = defaultdict(lambda: self.env['account.move.line'])
        rounded_globally = self.company_id.tax_calculation_rounding_method == 'round_globally'
        for order in self.order_ids:
            # Combine pos receivable lines
            # Separate cash payments for cash reconciliation later.
            for payment in order.payment_ids:
                amount, date = payment.amount, payment.payment_date
                if payment.payment_method_id.split_transactions:
                    if payment.payment_method_id.is_cash_count:
                        split_receivables_cash[payment] = self._update_amounts(split_receivables_cash[payment], {'amount': amount}, date)
                    else:
                        split_receivables[payment] = self._update_amounts(split_receivables[payment], {'amount': amount}, date)
                else:
                    key = payment.payment_method_id
                    if payment.payment_method_id.is_cash_count:
                        combine_receivables_cash[key] = self._update_amounts(combine_receivables_cash[key], {'amount': amount}, date)
                    else:
                        combine_receivables[key] = self._update_amounts(combine_receivables[key], {'amount': amount}, date)

            if order.is_invoiced:
                # Combine invoice receivable lines
                key = order.partner_id.property_account_receivable_id.id
                invoice_receivables[key] = self._update_amounts(invoice_receivables[key], {'amount': order.amount_total}, order.date_order)
                # side loop to gather receivable lines by account for reconciliation
                for move_line in order.account_move.line_ids.filtered(lambda aml: aml.account_id.internal_type == 'receivable'):
                    order_account_move_receivable_lines[move_line.account_id.id] |= move_line
            else:
                order_taxes = defaultdict(tax_amounts)
                for order_line in order.lines:
                    line = self._prepare_line(order_line)
                    # Combine sales/refund lines
                    sale_key = (
                        # account
                        line['income_account_id'],
                        # sign
                        -1 if line['amount'] < 0 else 1,
                        # for taxes
                        tuple((tax['id'], tax['account_id'], tax['tax_repartition_line_id']) for tax in line['taxes']),
                    )
                    sales[sale_key] = self._update_amounts(sales[sale_key], {'amount': line['amount']}, line['date_order'])
                    # Combine tax lines
                    for tax in line['taxes']:
                        tax_key = (tax['account_id'], tax['tax_repartition_line_id'], tax['id'], tuple(tax['tag_ids']))
                        order_taxes[tax_key] = self._update_amounts(
                            order_taxes[tax_key],
                            {'amount': tax['amount'], 'base_amount': tax['base']},
                            tax['date_order'],
                            round=not rounded_globally
                        )
                for tax_key, amounts in order_taxes.items():
                    if rounded_globally:
                        amounts = self._round_amounts(amounts)
                    for amount_key, amount in amounts.items():
                        taxes[tax_key][amount_key] += amount

                if self.company_id.anglo_saxon_accounting:
                    # Combine stock lines
                    stock_moves = self.env['stock.move'].search([
                        ('picking_id', '=', order.picking_id.id),
                        ('company_id.anglo_saxon_accounting', '=', True),
                        ('product_id.categ_id.property_valuation', '=', 'real_time')
                    ])
                    for move in stock_moves:
                        exp_key = move.product_id.property_account_expense_id or move.product_id.categ_id.property_account_expense_categ_id
                        out_key = move.product_id.categ_id.property_stock_account_output_categ_id
                        amount = -sum(move.stock_valuation_layer_ids.mapped('value'))
                        stock_expense[exp_key] = self._update_amounts(stock_expense[exp_key], {'amount': amount}, move.picking_id.date)
                        stock_output[out_key] = self._update_amounts(stock_output[out_key], {'amount': amount}, move.picking_id.date)

                # Increasing current partner's customer_rank
                order.partner_id._increase_rank('customer_rank')

        ## SECTION: Create non-reconcilable move lines
        # Create account.move.line records for
        #   - sales
        #   - taxes
        #   - stock expense
        #   - non-cash split receivables (not for automatic reconciliation)
        #   - non-cash combine receivables (not for automatic reconciliation)
        MoveLine = self.env['account.move.line'].with_context(check_move_validity=False)

        tax_vals = [self._get_tax_vals(key, amounts['amount'], amounts['amount_converted'], amounts['base_amount']) for key, amounts in taxes.items() if amounts['amount']]
        # Check if all taxes lines have account_id assigned. If not, there are repartition lines of the tax that have no account_id.
        tax_names_no_account = [line['name'] for line in tax_vals if line['account_id'] == False]
        if len(tax_names_no_account) > 0:
            error_message = _(
                'Unable to close and validate the session.\n'
                'Please set corresponding tax account in each repartition line of the following taxes: \n%s'
            ) % ', '.join(tax_names_no_account)
            raise UserError(error_message)

        MoveLine.create(
            tax_vals
            + [self._get_sale_vals(key, amounts['amount'], amounts['amount_converted']) for key, amounts in sales.items()]
            + [self._get_stock_expense_vals(key, amounts['amount'], amounts['amount_converted']) for key, amounts in stock_expense.items()]
            + [self._get_split_receivable_vals(key, amounts['amount'], amounts['amount_converted']) for key, amounts in split_receivables.items()]
            + [self._get_combine_receivable_vals(key, amounts['amount'], amounts['amount_converted']) for key, amounts in combine_receivables.items()]
        )

        ## SECTION: Create cash statement lines and cash move lines
        # Create the split and combine cash statement lines and account move lines.
        # Keep the reference by statement for reconciliation.
        # `split_cash_statement_lines` maps `statement` -> split cash statement lines
        # `combine_cash_statement_lines` maps `statement` -> combine cash statement lines
        # `split_cash_receivable_lines` maps `statement` -> split cash receivable lines
        # `combine_cash_receivable_lines` maps `statement` -> combine cash receivable lines
        statements_by_journal_id = {statement.journal_id.id: statement for statement in self.statement_ids}
        # handle split cash payments
        split_cash_statement_line_vals = defaultdict(list)
        split_cash_receivable_vals = defaultdict(list)
        for payment, amounts in split_receivables_cash.items():
            statement = statements_by_journal_id[payment.payment_method_id.cash_journal_id.id]
            split_cash_statement_line_vals[statement].append(self._get_statement_line_vals(statement, payment.payment_method_id.receivable_account_id, amounts['amount']))
            split_cash_receivable_vals[statement].append(self._get_split_receivable_vals(payment, amounts['amount'], amounts['amount_converted']))
        # handle combine cash payments
        combine_cash_statement_line_vals = defaultdict(list)
        combine_cash_receivable_vals = defaultdict(list)
        for payment_method, amounts in combine_receivables_cash.items():
            if not float_is_zero(amounts['amount'] , precision_rounding=self.currency_id.rounding):
                statement = statements_by_journal_id[payment_method.cash_journal_id.id]
                combine_cash_statement_line_vals[statement].append(self._get_statement_line_vals(statement, payment_method.receivable_account_id, amounts['amount']))
                combine_cash_receivable_vals[statement].append(self._get_combine_receivable_vals(payment_method, amounts['amount'], amounts['amount_converted']))
        # create the statement lines and account move lines
        BankStatementLine = self.env['account.bank.statement.line']
        split_cash_statement_lines = {}
        combine_cash_statement_lines = {}
        split_cash_receivable_lines = {}
        combine_cash_receivable_lines = {}
        for statement in self.statement_ids:
            split_cash_statement_lines[statement] = BankStatementLine.create(split_cash_statement_line_vals[statement])
            combine_cash_statement_lines[statement] = BankStatementLine.create(combine_cash_statement_line_vals[statement])
            split_cash_receivable_lines[statement] = MoveLine.create(split_cash_receivable_vals[statement])
            combine_cash_receivable_lines[statement] = MoveLine.create(combine_cash_receivable_vals[statement])

        ## SECTION: Create invoice receivable lines for this session's move_id.
        # Keep reference of the invoice receivable lines because
        # they are reconciled with the lines in order_account_move_receivable_lines
        invoice_receivable_vals = defaultdict(list)
        invoice_receivable_lines = {}
        for receivable_account_id, amounts in invoice_receivables.items():
            invoice_receivable_vals[receivable_account_id].append(self._get_invoice_receivable_vals(receivable_account_id, amounts['amount'], amounts['amount_converted']))
        for receivable_account_id, vals in invoice_receivable_vals.items():
            invoice_receivable_lines[receivable_account_id] = MoveLine.create(vals)

        ## SECTION: Create stock output lines
        # Keep reference to the stock output lines because
        # they are reconciled with output lines in the stock.move's account.move.line
        stock_output_vals = defaultdict(list)
        stock_output_lines = {}
        for output_account, amounts in stock_output.items():
            stock_output_vals[output_account].append(self._get_stock_output_vals(output_account, amounts['amount'], amounts['amount_converted']))
        for output_account, vals in stock_output_vals.items():
            stock_output_lines[output_account] = MoveLine.create(vals)

        ## SECTION: Reconcile account move lines
        # reconcile cash receivable lines
        for statement in self.statement_ids:
            if not self.config_id.cash_control:
                statement.write({'balance_end_real': statement.balance_end})
            statement.button_confirm_bank()
            all_lines = (
                  split_cash_statement_lines[statement].mapped('journal_entry_ids').filtered(lambda aml: aml.account_id.internal_type == 'receivable')
                | combine_cash_statement_lines[statement].mapped('journal_entry_ids').filtered(lambda aml: aml.account_id.internal_type == 'receivable')
                | split_cash_receivable_lines[statement]
                | combine_cash_receivable_lines[statement]
            )
            accounts = all_lines.mapped('account_id')
            lines_by_account = [all_lines.filtered(lambda l: l.account_id == account) for account in accounts]
            for lines in lines_by_account:
                lines.reconcile()

        # reconcile invoice receivable lines
        for account_id in order_account_move_receivable_lines:
            ( order_account_move_receivable_lines[account_id]
            | invoice_receivable_lines[account_id]
            ).reconcile()

        # reconcile stock output lines
        stock_moves = self.env['stock.move'].search([('picking_id', 'in', self.order_ids.filtered(lambda order: not order.is_invoiced).mapped('picking_id').ids)])
        stock_account_move_lines = self.env['account.move'].search([('stock_move_id', 'in', stock_moves.ids)]).mapped('line_ids')
        for account_id in stock_output_lines:
            ( stock_output_lines[account_id]
            | stock_account_move_lines.filtered(lambda aml: aml.account_id == account_id)
            ).reconcile()

    def _prepare_line(self, order_line):
        """ Derive from order_line the order date, income account, amount and taxes information.

        These information will be used in accumulating the amounts for sales and tax lines.
        """
        def get_income_account(order_line):
            product = order_line.product_id
            income_account = product.with_context(force_company=order_line.company_id.id).property_account_income_id or product.categ_id.with_context(force_company=order_line.company_id.id).property_account_income_categ_id
            if not income_account:
                raise UserError(_('Please define income account for this product: "%s" (id:%d).')
                                % (product.name, product.id))
            return order_line.order_id.fiscal_position_id.map_account(income_account)

        tax_ids = order_line.tax_ids_after_fiscal_position\
                    .filtered(lambda t: t.company_id.id == order_line.order_id.company_id.id)
        price = order_line.price_unit * (1 - (order_line.discount or 0.0) / 100.0)
        taxes = tax_ids.compute_all(price_unit=price, quantity=order_line.qty, currency=self.currency_id, is_refund=order_line.qty<0).get('taxes', [])
        date_order = order_line.order_id.date_order
        taxes = [{'date_order': date_order, **tax} for tax in taxes]
        return {
            'date_order': order_line.order_id.date_order,
            'income_account_id': get_income_account(order_line).id,
            'amount': order_line.price_subtotal,
            'taxes': taxes,
        }

    def _get_split_receivable_vals(self, payment, amount, amount_converted):
        partial_vals = {
            'account_id': payment.payment_method_id.receivable_account_id.id,
            'move_id': self.move_id.id,
            'partner_id': self.env["res.partner"]._find_accounting_partner(payment.partner_id).id,
            'name': '%s - %s' % (self.name, payment.payment_method_id.name),
        }
        return self._debit_amounts(partial_vals, amount, amount_converted)

    def _get_combine_receivable_vals(self, payment_method, amount, amount_converted):
        partial_vals = {
            'account_id': payment_method.receivable_account_id.id,
            'move_id': self.move_id.id,
            'name': '%s - %s' % (self.name, payment_method.name)
        }
        return self._debit_amounts(partial_vals, amount, amount_converted)

    def _get_invoice_receivable_vals(self, account_id, amount, amount_converted):
        partial_vals = {
            'account_id': account_id,
            'move_id': self.move_id.id,
            'name': 'From invoiced orders'
        }
        return self._credit_amounts(partial_vals, amount, amount_converted)

    def _get_sale_vals(self, key, amount, amount_converted):
        account_id, sign, tax_keys = key
        tax_ids = set(tax[0] for tax in tax_keys)
        applied_taxes = self.env['account.tax'].browse(tax_ids)
        title = 'Sales' if sign == 1 else 'Refund'
        name = '%s untaxed' % title
        if applied_taxes:
            name = '%s with %s' % (title, ', '.join([tax.name for tax in applied_taxes]))
        base_tags = applied_taxes\
            .mapped('invoice_repartition_line_ids' if sign == 1 else 'refund_repartition_line_ids')\
            .filtered(lambda line: line.repartition_type == 'base')\
            .tag_ids
        partial_vals = {
            'name': name,
            'account_id': account_id,
            'move_id': self.move_id.id,
            'tax_ids': [(6, 0, tax_ids)],
            'tag_ids': [(6, 0, base_tags.ids)],
        }
        return self._credit_amounts(partial_vals, amount, amount_converted)

    def _get_tax_vals(self, key, amount, amount_converted, base_amount):
        account_id, repartition_line_id, tax_id, tag_ids = key
        tax = self.env['account.tax'].browse(tax_id)
        partial_args = {
            'name': tax.name,
            'account_id': account_id,
            'move_id': self.move_id.id,
            'tax_base_amount': base_amount,
            'tax_repartition_line_id': repartition_line_id,
            'tag_ids': [(6, 0, tag_ids)],
        }
        return self._credit_amounts(partial_args, amount, amount_converted)

    def _get_stock_expense_vals(self, exp_account, amount, amount_converted):
        partial_args = {'account_id': exp_account.id, 'move_id': self.move_id.id}
        return self._debit_amounts(partial_args, amount, amount_converted)

    def _get_stock_output_vals(self, out_account, amount, amount_converted):
        partial_args = {'account_id': out_account.id, 'move_id': self.move_id.id}
        return self._credit_amounts(partial_args, amount, amount_converted)

    def _get_statement_line_vals(self, statement, receivable_account, amount):
        return {
            'date': fields.Date.context_today(self),
            'amount': amount,
            'name': self.name,
            'statement_id': statement.id,
            'account_id': receivable_account.id,
        }

    def _update_amounts(self, old_amounts, amounts_to_add, date, round=True):
        new_amounts = {}
        for k, amount in old_amounts.items():
            if k == 'amount_converted':
                amount_converted = old_amounts['amount_converted']
                amount_to_convert = amounts_to_add['amount']
                new_amounts['amount_converted'] = amount_converted if self.is_in_company_currency else (amount_converted + self._amount_converter(amount_to_convert, date, round))
            else:
                new_amounts[k] = old_amounts[k] + amounts_to_add[k]
        return new_amounts

    def _round_amounts(self, amounts):
        new_amounts = {}
        for key, amount in amounts.items():
            if key == 'amount_converted':
                # round the amount_converted using the company currency.
                new_amounts[key] = self.company_id.currency_id.round(amount)
            else:
                new_amounts[key] = self.currency_id.round(amount)
        return new_amounts

    def _credit_amounts(self, partial_move_line_vals, amount, amount_converted):
        """ `partial_move_line_vals` is completed by `credit`ing the given amounts.

        NOTE Amounts in PoS are in the currency of journal_id in the session.config_id.
        This means that amount fields in any pos record are actually equivalent to amount_currency
        in account module. Understanding this basic is important in correctly assigning values for
        'amount' and 'amount_currency' in the account.move.line record.

        :param partial_move_line_vals dict:
            initial values in creating account.move.line
        :param amount float:
            amount derived from pos.payment, pos.order, or pos.order.line records
        :param amount_converted float:
            converted value of `amount` from the given `session_currency` to company currency

        :return dict: complete values for creating 'amount.move.line' record
        """
        if self.is_in_company_currency:
            return {
                'debit': -amount if amount < 0.0 else 0.0,
                'credit': amount if amount > 0.0 else 0.0,
                **partial_move_line_vals
            }
        else:
            return {
                'debit': -amount_converted if amount_converted < 0.0 else 0.0,
                'credit': amount_converted if amount_converted > 0.0 else 0.0,
                'amount_currency': -amount if amount_converted > 0 else amount,
                'currency_id': self.currency_id.id,
                **partial_move_line_vals
            }

    def _debit_amounts(self, partial_move_line_vals, amount, amount_converted):
        """ `partial_move_line_vals` is completed by `debit`ing the given amounts.

        See _credit_amounts docs for more details.
        """
        if self.is_in_company_currency:
            return {
                'debit': amount if amount > 0.0 else 0.0,
                'credit': -amount if amount < 0.0 else 0.0,
                **partial_move_line_vals
            }
        else:
            return {
                'debit': amount_converted if amount_converted > 0.0 else 0.0,
                'credit': -amount_converted if amount_converted < 0.0 else 0.0,
                'amount_currency': amount if amount_converted > 0 else -amount,
                'currency_id': self.currency_id.id,
                **partial_move_line_vals
            }

    def _amount_converter(self, amount, date, round):
        # self should be single record as this method is only called in the subfunctions of self._validate_session
        return self.currency_id._convert(amount, self.company_id.currency_id, self.company_id, date, round=round)

    def show_journal_items(self):
        self.ensure_one()
        all_related_moves = self._get_related_account_moves()
        return {
            'name': _('Journal Items'),
            'type': 'ir.actions.act_window',
            'res_model': 'account.move.line',
            'view_mode': 'tree',
            'view_id':self.env.ref('account.view_move_line_tree_grouped').id,
            'domain': [('id', 'in', all_related_moves.mapped('line_ids').ids)],
            'context': {
                'journal_type':'general',
                'search_default_group_by_move': 1,
                'group_by':'move_id', 'search_default_posted':1,
                'name_groupby':1,
            },
        }

    def _get_related_account_moves(self):
        def get_matched_move_lines(aml):
            if aml.credit > 0:
                return [r.debit_move_id.id for r in aml.matched_debit_ids]
            else:
                return [r.credit_move_id.id for r in aml.matched_credit_ids]

        session_move = self.move_id
        # get all the linked move lines to this account move.
        non_reconcilable_lines = session_move.line_ids.filtered(lambda aml: not aml.account_id.reconcile)
        reconcilable_lines = session_move.line_ids - non_reconcilable_lines
        fully_reconciled_lines = reconcilable_lines.filtered(lambda aml: aml.full_reconcile_id)
        partially_reconciled_lines = reconcilable_lines - fully_reconciled_lines

        cash_move_lines = self.env['account.move.line'].search([('statement_id', '=', self.cash_register_id.id)])

        ids = (non_reconcilable_lines.ids
                + fully_reconciled_lines.mapped('full_reconcile_id').mapped('reconciled_line_ids').ids
                + sum(partially_reconciled_lines.mapped(get_matched_move_lines), partially_reconciled_lines.ids)
                + cash_move_lines.ids)

        return self.env['account.move.line'].browse(ids).mapped('move_id')

    def action_show_payments_list(self):
        return {
            'name': _('Payments'),
            'type': 'ir.actions.act_window',
            'res_model': 'pos.payment',
            'view_mode': 'tree,form',
            'domain': [('session_id', '=', self.id)],
            'context': {'search_default_group_by_payment_method': 1}
        }

    def open_frontend_cb(self):
        """Open the pos interface with config_id as an extra argument.

        In vanilla PoS each user can only have one active session, therefore it was not needed to pass the config_id
        on opening a session. It is also possible to login to sessions created by other users.

        :returns: dict
        """
        if not self.ids:
            return {}
        return {
            'type': 'ir.actions.act_url',
            'target': 'self',
            'url':   '/pos/web?config_id=%d' % self.config_id.id,
        }

    def open_cashbox_pos(self):
        self.ensure_one()
        action = self.cash_register_id.open_cashbox_id()
        action['view_id'] = self.env.ref('point_of_sale.view_account_bnk_stmt_cashbox_footer').id
        action['context']['pos_session_id'] = self.id
        action['context']['default_pos_id'] = self.config_id.id
        return action

    def action_view_order(self):
        return {
            'name': _('Orders'),
            'res_model': 'pos.order',
            'view_mode': 'tree,form',
            'views': [
                (self.env.ref('point_of_sale.view_pos_order_tree_no_session_id').id, 'tree'),
                (self.env.ref('point_of_sale.view_pos_pos_form').id, 'form'),
                ],
            'type': 'ir.actions.act_window',
            'domain': [('session_id', 'in', self.ids)],
        }

    @api.model
    def _alert_old_session(self):
        # If the session is open for more then one week,
        # log a next activity to close the session.
        sessions = self.search([('start_at', '<=', (fields.datetime.now() - timedelta(days=7))), ('state', '!=', 'closed')])
        for session in sessions:
            if self.env['mail.activity'].search_count([('res_id', '=', session.id), ('res_model', '=', 'pos.session')]) == 0:
                session.activity_schedule('point_of_sale.mail_activity_old_session',
                        user_id=session.user_id.id, note=_("Your PoS Session is open since ") + fields.Date.to_string(session.start_at)
                        + _(", we advise you to close it and to create a new one."))

    def _warning_balance_closing(self):
        self.ensure_one()

        context = dict(self._context)
        context['session_id'] = self.id

        return {
            'name': _('Balance control'),
            'view_type': 'form',
            'view_mode': 'form',
            'res_model': 'closing.balance.confirm.wizard',
            'views': [(False, 'form')],
            'type': 'ir.actions.act_window',
            'context': context,
            'target': 'new'
        }

    def _check_if_no_draft_orders(self):
        draft_orders = self.order_ids.filtered(lambda order: order.state == 'draft')
        if draft_orders:
            raise UserError(_(
                    'There are still orders in draft state in the session. '
                    'Pay or cancel the following orders to validate the session:\n%s'
                ) % ', '.join(draft_orders.mapped('name'))
            )
        return True
Пример #11
0
class FetchmailServer(models.Model):
    """Incoming POP/IMAP mail server account"""

    _name = 'fetchmail.server'
    _description = 'Incoming Mail Server'
    _order = 'priority'

    name = fields.Char('Name', required=True)
    active = fields.Boolean('Active', default=True)
    state = fields.Selection([
        ('draft', 'Not Confirmed'),
        ('done', 'Confirmed'),
    ], string='Status', index=True, readonly=True, copy=False, default='draft')
    server = fields.Char(string='Server Name', readonly=True, help="Hostname or IP of the mail server", states={'draft': [('readonly', False)]})
    port = fields.Integer(readonly=True, states={'draft': [('readonly', False)]})
    type = fields.Selection([
        ('pop', 'POP Server'),
        ('imap', 'IMAP Server'),
        ('local', 'Local Server'),
    ], 'Server Type', index=True, required=True, default='pop')
    is_ssl = fields.Boolean('SSL/TLS', help="Connections are encrypted with SSL/TLS through a dedicated port (default: IMAPS=993, POP3S=995)")
    attach = fields.Boolean('Keep Attachments', help="Whether attachments should be downloaded. "
                                                     "If not enabled, incoming emails will be stripped of any attachments before being processed", default=True)
    original = fields.Boolean('Keep Original', help="Whether a full original copy of each email should be kept for reference "
                                                    "and attached to each processed message. This will usually double the size of your message database.")
    date = fields.Datetime(string='Last Fetch Date', readonly=True)
    user = fields.Char(string='Username', readonly=True, states={'draft': [('readonly', False)]})
    password = fields.Char(readonly=True, states={'draft': [('readonly', False)]})
    object_id = fields.Many2one('ir.model', string="Create a New Record", help="Process each incoming mail as part of a conversation "
                                                                                "corresponding to this document type. This will create "
                                                                                "new documents for new conversations, or attach follow-up "
                                                                                "emails to the existing conversations (documents).")
    priority = fields.Integer(string='Server Priority', readonly=True, states={'draft': [('readonly', False)]}, help="Defines the order of processing, lower values mean higher priority", default=5)
    message_ids = fields.One2many('mail.mail', 'fetchmail_server_id', string='Messages', readonly=True)
    configuration = fields.Text('Configuration', readonly=True)
    script = fields.Char(readonly=True, default='/mail/static/scripts/openerp_mailgate.py')

    @api.onchange('type', 'is_ssl', 'object_id')
    def onchange_server_type(self):
        self.port = 0
        if self.type == 'pop':
            self.port = self.is_ssl and 995 or 110
        elif self.type == 'imap':
            self.port = self.is_ssl and 993 or 143
        else:
            self.server = ''

        conf = {
            'dbname': self.env.cr.dbname,
            'uid': self.env.uid,
            'model': self.object_id.model if self.object_id else 'MODELNAME'
        }
        self.configuration = """
            Use the below script with the following command line options with your Mail Transport Agent (MTA)
            openerp_mailgate.py --host=HOSTNAME --port=PORT -u %(uid)d -p PASSWORD -d %(dbname)s
            Example configuration for the postfix mta running locally:
            /etc/postfix/virtual_aliases:
            @youdomain openerp_mailgate@localhost
            /etc/aliases:
            openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p PASSWORD -d %(dbname)s"
        """ % conf

    @api.model
    def create(self, values):
        res = super(FetchmailServer, self).create(values)
        self._update_cron()
        return res

    @api.multi
    def write(self, values):
        res = super(FetchmailServer, self).write(values)
        self._update_cron()
        return res

    @api.multi
    def unlink(self):
        res = super(FetchmailServer, self).unlink()
        self._update_cron()
        return res

    @api.multi
    def set_draft(self):
        self.write({'state': 'draft'})
        return True

    @api.multi
    def connect(self):
        self.ensure_one()
        if self.type == 'imap':
            if self.is_ssl:
                connection = IMAP4_SSL(self.server, int(self.port))
            else:
                connection = IMAP4(self.server, int(self.port))
            connection.login(self.user, self.password)
        elif self.type == 'pop':
            if self.is_ssl:
                connection = POP3_SSL(self.server, int(self.port))
            else:
                connection = POP3(self.server, int(self.port))
            #TODO: use this to remove only unread messages
            #connection.user("recent:"+server.user)
            connection.user(self.user)
            connection.pass_(self.password)
        # Add timeout on socket
        connection.sock.settimeout(MAIL_TIMEOUT)
        return connection

    @api.multi
    def button_confirm_login(self):
        for server in self:
            try:
                connection = server.connect()
                server.write({'state': 'done'})
            except Exception as err:
                _logger.info("Failed to connect to %s server %s.", server.type, server.name, exc_info=True)
                raise UserError(_("Connection test failed: %s") % tools.ustr(err))
            finally:
                try:
                    if connection:
                        if server.type == 'imap':
                            connection.close()
                        elif server.type == 'pop':
                            connection.quit()
                except Exception:
                    # ignored, just a consequence of the previous exception
                    pass
        return True

    @api.model
    def _fetch_mails(self):
        """ Method called by cron to fetch mails from servers """
        return self.search([('state', '=', 'done'), ('type', 'in', ['pop', 'imap'])]).fetch_mail()

    @api.multi
    def fetch_mail(self):
        """ WARNING: meant for cron usage only - will commit() after each email! """
        additionnal_context = {
            'fetchmail_cron_running': True
        }
        MailThread = self.env['mail.thread']
        for server in self:
            _logger.info('start checking for new emails on %s server %s', server.type, server.name)
            additionnal_context['fetchmail_server_id'] = server.id
            additionnal_context['server_type'] = server.type
            count, failed = 0, 0
            imap_server = None
            pop_server = None
            if server.type == 'imap':
                try:
                    imap_server = server.connect()
                    imap_server.select()
                    result, data = imap_server.search(None, '(UNSEEN)')
                    for num in data[0].split():
                        res_id = None
                        result, data = imap_server.fetch(num, '(RFC822)')
                        imap_server.store(num, '-FLAGS', '\\Seen')
                        try:
                            res_id = MailThread.with_context(**additionnal_context).message_process(server.object_id.model, data[0][1], save_original=server.original, strip_attachments=(not server.attach))
                        except Exception:
                            _logger.info('Failed to process mail from %s server %s.', server.type, server.name, exc_info=True)
                            failed += 1
                        imap_server.store(num, '+FLAGS', '\\Seen')
                        self._cr.commit()
                        count += 1
                    _logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", count, server.type, server.name, (count - failed), failed)
                except Exception:
                    _logger.info("General failure when trying to fetch mail from %s server %s.", server.type, server.name, exc_info=True)
                finally:
                    if imap_server:
                        imap_server.close()
                        imap_server.logout()
            elif server.type == 'pop':
                try:
                    while True:
                        pop_server = server.connect()
                        (num_messages, total_size) = pop_server.stat()
                        pop_server.list()
                        for num in range(1, min(MAX_POP_MESSAGES, num_messages) + 1):
                            (header, messages, octets) = pop_server.retr(num)
                            message = (b'\n').join(messages)
                            res_id = None
                            try:
                                res_id = MailThread.with_context(**additionnal_context).message_process(server.object_id.model, message, save_original=server.original, strip_attachments=(not server.attach))
                                pop_server.dele(num)
                            except Exception:
                                _logger.info('Failed to process mail from %s server %s.', server.type, server.name, exc_info=True)
                                failed += 1
                            self.env.cr.commit()
                        if num_messages < MAX_POP_MESSAGES:
                            break
                        pop_server.quit()
                        _logger.info("Fetched %d email(s) on %s server %s; %d succeeded, %d failed.", num_messages, server.type, server.name, (num_messages - failed), failed)
                except Exception:
                    _logger.info("General failure when trying to fetch mail from %s server %s.", server.type, server.name, exc_info=True)
                finally:
                    if pop_server:
                        pop_server.quit()
            server.write({'date': fields.Datetime.now()})
        return True

    @api.model
    def _update_cron(self):
        if self.env.context.get('fetchmail_cron_running'):
            return
        try:
            # Enabled/Disable cron based on the number of 'done' server of type pop or imap
            cron = self.env.ref('fetchmail.ir_cron_mail_gateway_action')
            cron.toggle(model=self._name, domain=[('state', '=', 'done'), ('type', 'in', ['pop', 'imap'])])
        except ValueError:
            pass
Пример #12
0
class KsDashboardNinjaBoard(models.Model):
    _name = 'eagle_dashboard.board'
    _description = 'Dashboard Ninja'

    name = fields.Char(string="Dashboard Name", required=True, size=35)
    eagle_dashboard_items_ids = fields.One2many('eagle_dashboard.item',
                                                'eagle_dashboard_board_id',
                                                string='Dashboard Items')
    eagle_dashboard_menu_name = fields.Char(string="Menu Name")
    eagle_dashboard_top_menu_id = fields.Many2one(
        'ir.ui.menu',
        domain="[('parent_id','=',False)]",
        string="Show Under Menu")
    eagle_dashboard_client_action_id = fields.Many2one('ir.actions.client')
    eagle_dashboard_menu_id = fields.Many2one('ir.ui.menu')
    eagle_dashboard_state = fields.Char()
    eagle_dashboard_active = fields.Boolean(string="Active", default=True)
    eagle_dashboard_group_access = fields.Many2many('res.groups',
                                                    string="Group Access")

    # DateFilter Fields
    eagle_dashboard_start_date = fields.Datetime(string="Start Date")
    eagle_dashboard_end_date = fields.Datetime(string="End Date")
    eagle_date_filter_selection = fields.Selection(
        [
            ('l_none', 'All Time'),
            ('l_day', 'Today'),
            ('t_week', 'This Week'),
            ('t_month', 'This Month'),
            ('t_quarter', 'This Quarter'),
            ('t_year', 'This Year'),
            ('n_day', 'Next Day'),
            ('n_week', 'Next Week'),
            ('n_month', 'Next Month'),
            ('n_quarter', 'Next Quarter'),
            ('n_year', 'Next Year'),
            ('ls_day', 'Last Day'),
            ('ls_week', 'Last Week'),
            ('ls_month', 'Last Month'),
            ('ls_quarter', 'Last Quarter'),
            ('ls_year', 'Last Year'),
            ('l_week', 'Last 7 days'),
            ('l_month', 'Last 30 days'),
            ('l_quarter', 'Last 90 days'),
            ('l_year', 'Last 365 days'),
            ('l_custom', 'Custom Filter'),
        ],
        default='l_none',
        string="Default Date Filter")

    eagle_gridstack_config = fields.Char('Item Configurations')
    eagle_dashboard_default_template = fields.Many2one(
        'eagle_dashboard.board_template',
        default=lambda self: self.env.ref('eagle_dashboard.eagle_blank', False
                                          ),
        string="Dashboard Template")

    eagle_set_interval = fields.Selection(
        [
            (15000, '15 Seconds'),
            (30000, '30 Seconds'),
            (45000, '45 Seconds'),
            (60000, '1 minute'),
            (120000, '2 minute'),
            (300000, '5 minute'),
            (600000, '10 minute'),
        ],
        string="Default Update Interval",
        help="Update Interval for new items only")
    eagle_dashboard_menu_sequence = fields.Integer(
        string="Menu Sequence",
        default=10,
        help=
        "Smallest sequence give high priority and Highest sequence give low priority"
    )

    @api.model
    def create(self, vals):
        record = super(KsDashboardNinjaBoard, self).create(vals)
        if 'eagle_dashboard_top_menu_id' in vals and 'eagle_dashboard_menu_name' in vals:
            action_id = {
                'name': vals['eagle_dashboard_menu_name'] + " Action",
                'res_model': 'eagle_dashboard.board',
                'tag': 'eagle_dashboard',
                'params': {
                    'eagle_dashboard_id': record.id
                },
            }
            record.eagle_dashboard_client_action_id = self.env[
                'ir.actions.client'].sudo().create(action_id)

            record.eagle_dashboard_menu_id = self.env['ir.ui.menu'].sudo(
            ).create({
                'name':
                vals['eagle_dashboard_menu_name'],
                'active':
                vals.get('eagle_dashboard_active', True),
                'parent_id':
                vals['eagle_dashboard_top_menu_id'],
                'action':
                "ir.actions.client," +
                str(record.eagle_dashboard_client_action_id.id),
                'groups_id':
                vals.get('eagle_dashboard_group_access', False),
                'sequence':
                vals.get('eagle_dashboard_menu_sequence', 10)
            })

        if record.eagle_dashboard_default_template and record.eagle_dashboard_default_template.eagle_item_count:
            eagle_gridstack_config = {}
            template_data = json.loads(
                record.eagle_dashboard_default_template.eagle_gridstack_config)
            for item_data in template_data:
                dashboard_item = self.env.ref(item_data['item_id']).copy(
                    {'eagle_dashboard_board_id': record.id})
                eagle_gridstack_config[dashboard_item.id] = item_data['data']
            record.eagle_gridstack_config = json.dumps(eagle_gridstack_config)
        return record

    @api.multi
    def write(self, vals):
        record = super(KsDashboardNinjaBoard, self).write(vals)
        for rec in self:
            if 'eagle_dashboard_menu_name' in vals:
                if self.env.ref(
                        'eagle_dashboard.eagle_my_default_dashboard_board'
                ) and self.env.ref(
                        'eagle_dashboard.eagle_my_default_dashboard_board'
                ).sudo().id == rec.id:
                    if self.env.ref('eagle_dashboard.board_menu_root', False):
                        self.env.ref('eagle_dashboard.board_menu_root').sudo(
                        ).name = vals['eagle_dashboard_menu_name']
                else:
                    rec.eagle_dashboard_menu_id.sudo(
                    ).name = vals['eagle_dashboard_menu_name']
            if 'eagle_dashboard_group_access' in vals:
                if self.env.ref(
                        'eagle_dashboard.eagle_my_default_dashboard_board'
                ).id == rec.id:
                    if self.env.ref('eagle_dashboard.board_menu_root', False):
                        self.env.ref(
                            'eagle_dashboard.board_menu_root'
                        ).groups_id = vals['eagle_dashboard_group_access']
                else:
                    rec.eagle_dashboard_menu_id.sudo(
                    ).groups_id = vals['eagle_dashboard_group_access']
            if 'eagle_dashboard_active' in vals and rec.eagle_dashboard_menu_id:
                rec.eagle_dashboard_menu_id.sudo(
                ).active = vals['eagle_dashboard_active']

            if 'eagle_dashboard_top_menu_id' in vals:
                rec.eagle_dashboard_menu_id.write(
                    {'parent_id': vals['eagle_dashboard_top_menu_id']})

            if 'eagle_dashboard_menu_sequence' in vals:
                rec.eagle_dashboard_menu_id.sudo(
                ).sequence = vals['eagle_dashboard_menu_sequence']

        return record

    @api.multi
    def unlink(self):
        if self.env.ref('eagle_dashboard.eagle_my_default_dashboard_board'
                        ).id in self.ids:
            raise ValidationError(_("Default Dashboard can't be deleted."))
        else:
            for rec in self:
                rec.eagle_dashboard_client_action_id.sudo().unlink()
                rec.eagle_dashboard_menu_id.sudo().unlink()
                rec.eagle_dashboard_items_ids.unlink()
        res = super(KsDashboardNinjaBoard, self).unlink()
        return res

    @api.model
    def eagle_fetch_dashboard_data(self,
                                   eagle_dashboard_id,
                                   eagle_item_domain=False):
        """
        Return Dictionary of Dashboard Data.
        :param eagle_dashboard_id: Integer
        :param eagle_item_domain: List[List]
        :return: dict
        """

        self = self.eagle_set_date(eagle_dashboard_id)
        has_group_eagle_dashboard_manager = self.env.user.has_group(
            'eagle_dashboard.eagle_dashboard_group_manager')
        dashboard_data = {
            'name':
            self.browse(eagle_dashboard_id).name,
            'eagle_dashboard_manager':
            has_group_eagle_dashboard_manager,
            'eagle_dashboard_list':
            self.search_read([], ['id', 'name']),
            'eagle_dashboard_start_date':
            self._context.get('ksDateFilterStartDate', False),
            'eagle_dashboard_end_date':
            self._context.get('ksDateFilterEndDate', False),
            'eagle_date_filter_selection':
            self._context.get(
                'ksDateFilterSelection',
                self.browse(eagle_dashboard_id).eagle_date_filter_selection),
            'eagle_gridstack_config':
            self.browse(eagle_dashboard_id).eagle_gridstack_config,
            'eagle_set_interval':
            self.browse(eagle_dashboard_id).eagle_set_interval,
        }

        if len(self.browse(eagle_dashboard_id).eagle_dashboard_items_ids) < 1:
            dashboard_data['eagle_item_data'] = False
        else:
            if eagle_item_domain:
                try:
                    items = self.eagle_fetch_item(
                        self.eagle_dashboard_items_ids.search([[
                            'eagle_dashboard_board_id', '=', eagle_dashboard_id
                        ]] + eagle_item_domain).ids, eagle_dashboard_id)
                except Exception as e:
                    items = self.eagle_fetch_item(
                        self.browse(
                            eagle_dashboard_id).eagle_dashboard_items_ids.ids,
                        eagle_dashboard_id)
                    dashboard_data['eagle_item_data'] = items
                    return dashboard_data
            else:
                items = self.eagle_fetch_item(
                    self.browse(
                        eagle_dashboard_id).eagle_dashboard_items_ids.ids,
                    eagle_dashboard_id)

            dashboard_data['eagle_item_data'] = items
        return dashboard_data

    @api.model
    def eagle_fetch_item(self, item_list, eagle_dashboard_id):
        """
        :rtype: object
        :param item_list: list of item ids.
        :return: {'id':[item_data]}
        """
        self = self.eagle_set_date(eagle_dashboard_id)
        items = {}
        item_model = self.env['eagle_dashboard.item']
        for item_id in item_list:
            item = self.eagle_fetch_item_data(item_model.browse(item_id))
            items[item['id']] = item
        return items

    # fetching Item info (Divided to make function inherit easily)
    def eagle_fetch_item_data(self, rec):
        """
        :rtype: object
        :param item_id: item object
        :return: object with formatted item data
        """
        if rec.eagle_actions:
            action = {}
            action['name'] = rec.eagle_actions.name
            action['type'] = rec.eagle_actions.type
            action['res_model'] = rec.eagle_actions.res_model
            action['views'] = rec.eagle_actions.views
            action['view_mode'] = rec.eagle_actions.view_mode
            action['target'] = 'current'
        else:
            action = False
        item = {
            'name':
            rec.name if rec.name else
            rec.eagle_model_id.name if rec.eagle_model_id else "Name",
            'eagle_background_color':
            rec.eagle_background_color,
            'eagle_font_color':
            rec.eagle_font_color,
            # 'eagle_domain': rec.eagle_domain.replace('"%UID"', str(
            #     self.env.user.id)) if rec.eagle_domain and "%UID" in rec.eagle_domain else rec.eagle_domain,
            'eagle_domain':
            rec.eagle_convert_into_proper_domain(rec.eagle_domain, rec),
            'eagle_dashboard_id':
            rec.eagle_dashboard_board_id.id,
            'eagle_icon':
            rec.eagle_icon,
            'eagle_model_id':
            rec.eagle_model_id.id,
            'eagle_model_name':
            rec.eagle_model_name,
            'eagle_model_display_name':
            rec.eagle_model_id.name,
            'eagle_record_count_type':
            rec.eagle_record_count_type,
            'eagle_record_count':
            rec.eagle_record_count,
            'id':
            rec.id,
            'eagle_layout':
            rec.eagle_layout,
            'eagle_icon_select':
            rec.eagle_icon_select,
            'eagle_default_icon':
            rec.eagle_default_icon,
            'eagle_default_icon_color':
            rec.eagle_default_icon_color,
            # Pro Fields
            'eagle_dashboard_item_type':
            rec.eagle_dashboard_item_type,
            'eagle_chart_item_color':
            rec.eagle_chart_item_color,
            'eagle_chart_groupby_type':
            rec.eagle_chart_groupby_type,
            'eagle_chart_relation_groupby':
            rec.eagle_chart_relation_groupby.id,
            'eagle_chart_relation_groupby_name':
            rec.eagle_chart_relation_groupby.name,
            'eagle_chart_date_groupby':
            rec.eagle_chart_date_groupby,
            'eagle_record_field':
            rec.eagle_record_field.id if rec.eagle_record_field else False,
            'eagle_chart_data':
            rec.eagle_chart_data,
            'eagle_list_view_data':
            rec.eagle_list_view_data,
            'eagle_chart_data_count_type':
            rec.eagle_chart_data_count_type,
            'eagle_bar_chart_stacked':
            rec.eagle_bar_chart_stacked,
            'eagle_semi_circle_chart':
            rec.eagle_semi_circle_chart,
            'eagle_list_view_type':
            rec.eagle_list_view_type,
            'eagle_list_view_group_fields':
            rec.eagle_list_view_group_fields.ids
            if rec.eagle_list_view_group_fields else False,
            'eagle_previous_period':
            rec.eagle_previous_period,
            'eagle_kpi_data':
            rec.eagle_kpi_data,
            'eagle_goal_enable':
            rec.eagle_goal_enable,
            'eagle_model_id_2':
            rec.eagle_model_id_2.id,
            'eagle_record_field_2':
            rec.eagle_record_field_2.id,
            'eagle_data_comparison':
            rec.eagle_data_comparison,
            'eagle_target_view':
            rec.eagle_target_view,
            'eagle_date_filter_selection':
            rec.eagle_date_filter_selection,
            'eagle_show_data_value':
            rec.eagle_show_data_value,
            'eagle_update_items_data':
            rec.eagle_update_items_data,
            # 'action_id': rec.eagle_actions.id if rec.eagle_actions else False,
            'sequence':
            0,
            'max_sequnce':
            len(rec.eagle_action_lines) if rec.eagle_action_lines else False,
            'action':
            action
        }
        return item

    def eagle_set_date(self, eagle_dashboard_id):
        if self._context.get('ksDateFilterSelection', False):
            eagle_date_filter_selection = self._context[
                'ksDateFilterSelection']
            if eagle_date_filter_selection == 'l_custom':
                self = self.with_context(
                    ksDateFilterStartDate=fields.datetime.strptime(
                        self._context['ksDateFilterStartDate'],
                        "%Y-%m-%dT%H:%M:%S.%fz"))
                self = self.with_context(
                    ksDateFilterEndDate=fields.datetime.strptime(
                        self._context['ksDateFilterEndDate'],
                        "%Y-%m-%dT%H:%M:%S.%fz"))

        else:
            eagle_date_filter_selection = self.browse(
                eagle_dashboard_id).eagle_date_filter_selection
            self = self.with_context(ksDateFilterStartDate=self.browse(
                eagle_dashboard_id).eagle_dashboard_start_date)
            self = self.with_context(ksDateFilterEndDate=self.browse(
                eagle_dashboard_id).eagle_dashboard_end_date)
            self = self.with_context(
                ksDateFilterSelection=eagle_date_filter_selection)

        if eagle_date_filter_selection not in ['l_custom', 'l_none']:
            eagle_date_data = eagle_get_date(eagle_date_filter_selection)
            self = self.with_context(
                ksDateFilterStartDate=eagle_date_data["selected_start_date"])
            self = self.with_context(
                ksDateFilterEndDate=eagle_date_data["selected_end_date"])

        return self

    @api.multi
    def load_previous_data(self):

        for rec in self:
            if rec.eagle_dashboard_menu_id and rec.eagle_dashboard_menu_id.action._table == 'ir_act_window':
                action_id = {
                    'name': rec['eagle_dashboard_menu_name'] + " Action",
                    'res_model': 'eagle_dashboard.board',
                    'tag': 'eagle_dashboard',
                    'params': {
                        'eagle_dashboard_id': rec.id
                    },
                }
                rec.eagle_dashboard_client_action_id = self.env[
                    'ir.actions.client'].sudo().create(action_id)
                rec.eagle_dashboard_menu_id.write({
                    'action':
                    "ir.actions.client," +
                    str(rec.eagle_dashboard_client_action_id.id)
                })

    def eagle_view_items_view(self):
        self.ensure_one()
        return {
            'name':
            _("Dashboard Items"),
            'res_model':
            'eagle_dashboard.item',
            'view_mode':
            'tree,form',
            'view_type':
            'form',
            'views': [(False, 'tree'), (False, 'form')],
            'type':
            'ir.actions.act_window',
            'domain': [('eagle_dashboard_board_id', '!=', False)],
            'search_view_id':
            self.env.ref('eagle_dashboard.eagle_item_search_view').id,
            'context': {
                'search_default_eagle_dashboard_board_id': self.id,
                'group_by': 'eagle_dashboard_board_id',
            },
            'help':
            _('''<p class="o_view_nocontent_smiling_face">
                                        You can find all items related to Dashboard Here.</p>
                                    '''),
        }

    # fetching Item info (Divided to make function inherit easily)
    def eagle_export_item_data(self, rec):
        eagle_chart_measure_field = []
        eagle_chart_measure_field_2 = []
        for res in rec.eagle_chart_measure_field:
            eagle_chart_measure_field.append(res.name)
        for res in rec.eagle_chart_measure_field_2:
            eagle_chart_measure_field_2.append(res.name)

        eagle_list_view_group_fields = []
        for res in rec.eagle_list_view_group_fields:
            eagle_list_view_group_fields.append(res.name)

        eagle_goal_lines = []
        for res in rec.eagle_goal_lines:
            goal_line = {
                'eagle_goal_date':
                datetime.datetime.strftime(res.eagle_goal_date, '%b %d, %Y'),
                'eagle_goal_value':
                res.eagle_goal_value,
            }
            eagle_goal_lines.append(goal_line)

        eagle_action_lines = []
        for res in rec.eagle_action_lines:
            action_line = {
                'eagle_item_action_field': res.eagle_item_action_field.name,
                'eagle_item_action_date_groupby':
                res.eagle_item_action_date_groupby,
                'eagle_chart_type': res.eagle_chart_type,
                'sequence': res.sequence,
            }
            eagle_action_lines.append(action_line)

        eagle_list_view_field = []
        for res in rec.eagle_list_view_fields:
            eagle_list_view_field.append(res.name)
        item = {
            'name':
            rec.name if rec.name else
            rec.eagle_model_id.name if rec.eagle_model_id else "Name",
            'eagle_background_color':
            rec.eagle_background_color,
            'eagle_font_color':
            rec.eagle_font_color,
            'eagle_domain':
            rec.eagle_domain,
            'eagle_icon':
            rec.eagle_icon,
            'eagle_id':
            rec.id,
            'eagle_model_id':
            rec.eagle_model_name,
            'eagle_record_count':
            rec.eagle_record_count,
            'eagle_layout':
            rec.eagle_layout,
            'eagle_icon_select':
            rec.eagle_icon_select,
            'eagle_default_icon':
            rec.eagle_default_icon,
            'eagle_default_icon_color':
            rec.eagle_default_icon_color,
            'eagle_record_count_type':
            rec.eagle_record_count_type,
            # Pro Fields
            'eagle_dashboard_item_type':
            rec.eagle_dashboard_item_type,
            'eagle_chart_item_color':
            rec.eagle_chart_item_color,
            'eagle_chart_groupby_type':
            rec.eagle_chart_groupby_type,
            'eagle_chart_relation_groupby':
            rec.eagle_chart_relation_groupby.name,
            'eagle_chart_date_groupby':
            rec.eagle_chart_date_groupby,
            'eagle_record_field':
            rec.eagle_record_field.name,
            'eagle_chart_sub_groupby_type':
            rec.eagle_chart_sub_groupby_type,
            'eagle_chart_relation_sub_groupby':
            rec.eagle_chart_relation_sub_groupby.name,
            'eagle_chart_date_sub_groupby':
            rec.eagle_chart_date_sub_groupby,
            'eagle_chart_data_count_type':
            rec.eagle_chart_data_count_type,
            'eagle_chart_measure_field':
            eagle_chart_measure_field,
            'eagle_chart_measure_field_2':
            eagle_chart_measure_field_2,
            'eagle_list_view_fields':
            eagle_list_view_field,
            'eagle_list_view_group_fields':
            eagle_list_view_group_fields,
            'eagle_list_view_type':
            rec.eagle_list_view_type,
            'eagle_record_data_limit':
            rec.eagle_record_data_limit,
            'eagle_sort_by_order':
            rec.eagle_sort_by_order,
            'eagle_sort_by_field':
            rec.eagle_sort_by_field.name,
            'eagle_date_filter_field':
            rec.eagle_date_filter_field.name,
            'eagle_goal_enable':
            rec.eagle_goal_enable,
            'eagle_standard_goal_value':
            rec.eagle_standard_goal_value,
            'eagle_goal_liness':
            eagle_goal_lines,
            'eagle_date_filter_selection':
            rec.eagle_date_filter_selection,
            'eagle_item_start_date':
            datetime.datetime.strftime(rec.eagle_item_start_date, '%b %d, %Y')
            if rec.eagle_item_start_date else False,
            'eagle_item_end_date':
            datetime.datetime.strftime(rec.eagle_item_end_date, '%b %d, %Y')
            if rec.eagle_item_end_date else False,
            'eagle_date_filter_selection_2':
            rec.eagle_date_filter_selection_2,
            'eagle_item_start_date_2':
            datetime.datetime.strftime(rec.eagle_item_start_date_2,
                                       '%b %d, %Y')
            if rec.eagle_item_start_date_2 else False,
            'eagle_item_end_date_2':
            datetime.datetime.strftime(rec.eagle_item_end_date_2, '%b %d, %Y')
            if rec.eagle_item_end_date_2 else False,
            'eagle_previous_period':
            rec.eagle_previous_period,
            'eagle_target_view':
            rec.eagle_target_view,
            'eagle_data_comparison':
            rec.eagle_data_comparison,
            'eagle_record_count_type_2':
            rec.eagle_record_count_type_2,
            'eagle_record_field_2':
            rec.eagle_record_field_2.name,
            'eagle_model_id_2':
            rec.eagle_model_id_2.model,
            'eagle_date_filter_field_2':
            rec.eagle_date_filter_field_2.name,
            'eagle_action_liness':
            eagle_action_lines,
            'eagle_compare_period':
            rec.eagle_compare_period,
            'eagle_year_period':
            rec.eagle_year_period,
        }
        return item

    @api.model
    def eagle_dashboard_export(self, eagle_dashboard_ids):
        eagle_dashboard_data = []
        eagle_dashboard_export_data = {}
        eagle_dashboard_ids = json.loads(eagle_dashboard_ids)
        for eagle_dashboard_id in eagle_dashboard_ids:
            dashboard_data = {
                'name':
                self.browse(eagle_dashboard_id).name,
                'eagle_dashboard_menu_name':
                self.browse(eagle_dashboard_id).eagle_dashboard_menu_name,
                'eagle_gridstack_config':
                self.browse(eagle_dashboard_id).eagle_gridstack_config,
            }
            if len(self.browse(
                    eagle_dashboard_id).eagle_dashboard_items_ids) < 1:
                dashboard_data['eagle_item_data'] = False
            else:
                items = []
                for rec in self.browse(
                        eagle_dashboard_id).eagle_dashboard_items_ids:
                    item = self.eagle_export_item_data(rec)
                    items.append(item)

                dashboard_data['eagle_item_data'] = items

            eagle_dashboard_data.append(dashboard_data)

            eagle_dashboard_export_data = {
                'eagle_file_format': 'eagle_dashboard_export_file',
                'eagle_dashboard_data': eagle_dashboard_data
            }
        return eagle_dashboard_export_data

    @api.model
    def eagle_import_dashboard(self, file):
        try:
            # eagle_dashboard_data = json.loads(file)
            eagle_dashboard_file_read = json.loads(file)
        except:
            raise ValidationError(_("This file is not supported"))

        if 'eagle_file_format' in eagle_dashboard_file_read and eagle_dashboard_file_read[
                'eagle_file_format'] == 'eagle_dashboard_export_file':
            eagle_dashboard_data = eagle_dashboard_file_read[
                'eagle_dashboard_data']
        else:
            raise ValidationError(
                _("Current Json File is not properly formatted according to Dashboard Ninja Model."
                  ))

        eagle_dashboard_key = [
            'name', 'eagle_dashboard_menu_name', 'eagle_gridstack_config'
        ]
        eagle_dashboard_item_key = [
            'eagle_model_id', 'eagle_chart_measure_field',
            'eagle_list_view_fields', 'eagle_record_field',
            'eagle_chart_relation_groupby', 'eagle_id'
        ]

        # Fetching dashboard model info
        for data in eagle_dashboard_data:
            if not all(key in data for key in eagle_dashboard_key):
                raise ValidationError(
                    _("Current Json File is not properly formatted according to Dashboard Ninja Model."
                      ))
            vals = {
                'name':
                data['name'],
                'eagle_dashboard_menu_name':
                data['eagle_dashboard_menu_name'],
                'eagle_dashboard_top_menu_id':
                self.env.ref("eagle_dashboard.board_menu_root").id,
                'eagle_dashboard_active':
                True,
                'eagle_gridstack_config':
                data['eagle_gridstack_config'],
                'eagle_dashboard_default_template':
                self.env.ref("eagle_dashboard.eagle_blank").id,
                'eagle_dashboard_group_access':
                False,
            }
            # Creating Dashboard
            dashboard_id = self.create(vals)

            if data['eagle_gridstack_config']:
                eagle_gridstack_config = eval(data['eagle_gridstack_config'])
            eagle_grid_stack_config = {}

            if data['eagle_item_data']:
                # Fetching dashboard item info
                for item in data['eagle_item_data']:
                    if not all(key in item
                               for key in eagle_dashboard_item_key):
                        raise ValidationError(
                            _("Current Json File is not properly formatted according to Dashboard Ninja Model."
                              ))

                    eagle_model = item['eagle_model_id'].replace(".", "_")

                    eagle_measure_field_ids = []
                    eagle_measure_field_2_ids = []

                    model = self.env['ir.model'].search([
                        ('model', '=', item['eagle_model_id'])
                    ])

                    if not model:
                        raise ValidationError(
                            _("Please Install the Module which contains the following Model : %s "
                              % item['eagle_model_id']))

                    if item['eagle_model_id_2']:
                        model_2 = self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id_2'])
                        ])
                        if not model_2:
                            raise ValidationError(
                                _("Please Install the Module which contains the following Model : %s "
                                  % item['eagle_model_id_2']))

                    for eagle_measure in item['eagle_chart_measure_field']:
                        for x in self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id'])
                        ]).modules.split(", "):
                            measure_id = x + '.field_' + eagle_model + "__" + eagle_measure
                            eagle_measure_id = self.env.ref(measure_id, False)
                            if eagle_measure_id:
                                eagle_measure_field_ids.append(
                                    eagle_measure_id.id)
                    item['eagle_chart_measure_field'] = [
                        (6, 0, eagle_measure_field_ids)
                    ]

                    for eagle_measure in item['eagle_chart_measure_field_2']:
                        for x in self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id'])
                        ]).modules.split(", "):
                            measure_id = x + '.field_' + eagle_model + "__" + eagle_measure
                            eagle_measure_id = self.env.ref(measure_id, False)
                            if eagle_measure_id:
                                eagle_measure_field_2_ids.append(
                                    eagle_measure_id.id)
                    item['eagle_chart_measure_field_2'] = [
                        (6, 0, eagle_measure_field_2_ids)
                    ]

                    eagle_list_view_group_fields = []
                    for eagle_measure in item['eagle_list_view_group_fields']:
                        for x in self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id'])
                        ]).modules.split(", "):
                            measure_id = x + '.field_' + eagle_model + "__" + eagle_measure
                            eagle_measure_id = self.env.ref(measure_id, False)
                            if eagle_measure_id:
                                eagle_list_view_group_fields.append(
                                    eagle_measure_id.id)
                    item['eagle_list_view_group_fields'] = [
                        (6, 0, eagle_list_view_group_fields)
                    ]

                    eagle_list_view_field_ids = []
                    for eagle_list_field in item['eagle_list_view_fields']:
                        for x in self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id'])
                        ]).modules.split(", "):
                            list_field_id = x + '.field_' + eagle_model + "__" + eagle_list_field
                            eagle_list_field_id = self.env.ref(
                                list_field_id, False)
                            if eagle_list_field_id:
                                eagle_list_view_field_ids.append(
                                    eagle_list_field_id.id)
                    item['eagle_list_view_fields'] = [
                        (6, 0, eagle_list_view_field_ids)
                    ]

                    if item['eagle_record_field']:
                        eagle_record_field = item['eagle_record_field']
                        for x in self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id'])
                        ]).modules.split(", "):
                            record_id = x + '.field_' + eagle_model + "__" + eagle_record_field
                            eagle_record_id = self.env.ref(record_id, False)
                            if eagle_record_id:
                                item['eagle_record_field'] = eagle_record_id.id

                    if item['eagle_date_filter_field']:
                        eagle_date_filter_field = item[
                            'eagle_date_filter_field']
                        for x in self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id'])
                        ]).modules.split(", "):
                            record_id = x + '.field_' + eagle_model + "__" + eagle_date_filter_field
                            eagle_record_id = self.env.ref(record_id, False)
                            if eagle_record_id:
                                item[
                                    'eagle_date_filter_field'] = eagle_record_id.id

                    if item['eagle_chart_relation_groupby']:
                        eagle_group_by = item['eagle_chart_relation_groupby']
                        for x in self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id'])
                        ]).modules.split(", "):
                            field_id = x + '.field_' + eagle_model + "__" + eagle_group_by
                            eagle_chart_relation_groupby = self.env.ref(
                                field_id, False)
                            if eagle_chart_relation_groupby:
                                item[
                                    'eagle_chart_relation_groupby'] = eagle_chart_relation_groupby.id

                    if item['eagle_chart_relation_sub_groupby']:
                        eagle_group_by = item[
                            'eagle_chart_relation_sub_groupby']
                        for x in self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id'])
                        ]).modules.split(", "):
                            field_id = x + '.field_' + eagle_model + "__" + eagle_group_by
                            eagle_chart_relation_sub_groupby = self.env.ref(
                                field_id, False)
                            if eagle_chart_relation_sub_groupby:
                                item[
                                    'eagle_chart_relation_sub_groupby'] = eagle_chart_relation_sub_groupby.id

                    # Sort by field : Many2one Entery
                    if item['eagle_sort_by_field']:
                        eagle_group_by = item['eagle_sort_by_field']
                        for x in self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id'])
                        ]).modules.split(", "):
                            field_id = x + '.field_' + eagle_model + "__" + eagle_group_by
                            eagle_sort_by_field = self.env.ref(field_id, False)
                            if eagle_sort_by_field:
                                item[
                                    'eagle_sort_by_field'] = eagle_sort_by_field.id

                    eagle_model_id = self.env['ir.model'].search([
                        ('model', '=', item['eagle_model_id'])
                    ]).id
                    if (item['eagle_model_id_2']):
                        eagle_model_2 = item['eagle_model_id_2'].replace(
                            ".", "_")
                        eagle_model_id_2 = self.env['ir.model'].search([
                            ('model', '=', item['eagle_model_id_2'])
                        ]).id
                        if item['eagle_record_field_2']:
                            eagle_record_field = item['eagle_record_field_2']
                            for x in self.env['ir.model'].search([
                                ('model', '=', item['eagle_model_id_2'])
                            ]).modules.split(", "):
                                record_id = x + '.field_' + eagle_model_2 + "__" + eagle_record_field
                                eagle_record_id = self.env.ref(
                                    record_id, False)
                                if eagle_record_id:
                                    item[
                                        'eagle_record_field_2'] = eagle_record_id.id
                        if item['eagle_date_filter_field_2']:
                            eagle_date_filter_field = item[
                                'eagle_date_filter_field_2']
                            for x in self.env['ir.model'].search([
                                ('model', '=', item['eagle_model_id_2'])
                            ]).modules.split(", "):
                                record_id = x + '.field_' + eagle_model_2 + "__" + eagle_date_filter_field
                                eagle_record_id = self.env.ref(
                                    record_id, False)
                                if eagle_record_id:
                                    item[
                                        'eagle_date_filter_field_2'] = eagle_record_id.id
                        item['eagle_model_id_2'] = eagle_model_id_2
                    else:
                        item['eagle_date_filter_field_2'] = False
                        item['eagle_record_field_2'] = False
                    eagle_model_id = item['eagle_model_id']
                    item['eagle_model_id'] = self.env['ir.model'].search([
                        ('model', '=', item['eagle_model_id'])
                    ]).id
                    item['eagle_dashboard_board_id'] = dashboard_id.id

                    eagle_goal_lines = item['eagle_goal_liness'].copy(
                    ) if item.get('eagle_goal_liness', False) else False
                    item['eagle_goal_liness'] = False

                    item['eagle_item_start_date'] = datetime.datetime.strptime(item['eagle_item_start_date'], '%b %d, %Y') if \
                        item[
                            'eagle_item_start_date'] else False
                    item['eagle_item_end_date'] = datetime.datetime.strptime(item['eagle_item_end_date'], '%b %d, %Y') if \
                        item[
                            'eagle_item_end_date'] else False
                    item['eagle_item_start_date_2'] = datetime.datetime.strptime(item['eagle_item_start_date_2'],
                                                                              '%b %d, %Y') if \
                        item[
                            'eagle_item_start_date_2'] else False
                    item['eagle_item_end_date_2'] = datetime.datetime.strptime(item['eagle_item_end_date_2'], '%b %d, %Y') if \
                        item[
                            'eagle_item_end_date_2'] else False

                    eagle_action_lines = item['eagle_action_liness'].copy(
                    ) if item.get('eagle_action_liness', False) else False
                    # Creating dashboard items
                    eagle_item = self.env['eagle_dashboard.item'].create(item)
                    if eagle_goal_lines and len(eagle_goal_lines) != 0:
                        for line in eagle_goal_lines:
                            line[
                                'eagle_goal_date'] = datetime.datetime.strptime(
                                    line['eagle_goal_date'], '%b %d, %Y')
                            line['eagle_dashboard_item'] = eagle_item.id
                            self.env['eagle_dashboard.item_goal'].create(line)

                    if eagle_action_lines and len(eagle_action_lines) != 0:

                        for line in eagle_action_lines:
                            if line['eagle_item_action_field']:
                                eagle_item_action_field = line[
                                    'eagle_item_action_field']
                                for x in self.env['ir.model'].search([
                                    ('model', '=', eagle_model_id)
                                ]).modules.split(", "):
                                    record_id = x + '.field_' + eagle_model + "__" + eagle_item_action_field
                                    eagle_record_id = self.env.ref(
                                        record_id, False)
                                    if eagle_record_id:
                                        line[
                                            'eagle_item_action_field'] = eagle_record_id.id
                            line['eagle_dashboard_item_id'] = eagle_item.id
                            self.env['eagle_dashboard.item_action'].create(
                                line)

                    if data['eagle_gridstack_config'] and str(
                            item['eagle_id']) in eagle_gridstack_config:
                        eagle_grid_stack_config[str(
                            eagle_item.id)] = eagle_gridstack_config[str(
                                item['eagle_id'])]

                self.browse(dashboard_id.id).write({
                    'eagle_gridstack_config':
                    json.dumps(eagle_grid_stack_config)
                })

        return "Success"
class ImLivechatReportChannel(models.Model):
    """ Livechat Support Report on the Channels """

    _name = "im_livechat.report.channel"
    _description = "Livechat Support Channel Report"
    _order = 'start_date, technical_name'
    _auto = False

    uuid = fields.Char('UUID', readonly=True)
    channel_id = fields.Many2one('mail.channel', 'Conversation', readonly=True)
    channel_name = fields.Char('Channel Name', readonly=True)
    technical_name = fields.Char('Code', readonly=True)
    livechat_channel_id = fields.Many2one('im_livechat.channel',
                                          'Channel',
                                          readonly=True)
    start_date = fields.Datetime('Start Date of session',
                                 readonly=True,
                                 help="Start date of the conversation")
    start_hour = fields.Char('Start Hour of session',
                             readonly=True,
                             help="Start hour of the conversation")
    day_number = fields.Char(
        'Day Number',
        readonly=True,
        help="Day number of the session (1 is Monday, 7 is Sunday)")
    time_to_answer = fields.Float(
        'Time to answer (sec)',
        digits=(16, 2),
        readonly=True,
        group_operator="avg",
        help="Average time in seconds to give the first answer to the visitor")
    start_date_hour = fields.Char('Hour of start Date of session',
                                  readonly=True)
    duration = fields.Float('Average duration',
                            digits=(16, 2),
                            readonly=True,
                            group_operator="avg",
                            help="Duration of the conversation (in seconds)")
    nbr_speaker = fields.Integer('# of speakers',
                                 readonly=True,
                                 group_operator="avg",
                                 help="Number of different speakers")
    nbr_message = fields.Integer('Average message',
                                 readonly=True,
                                 group_operator="avg",
                                 help="Number of message in the conversation")
    is_without_answer = fields.Integer(
        'Session(s) without answer',
        readonly=True,
        group_operator="sum",
        help="""A session is without answer if the operator did not answer. 
                                       If the visitor is also the operator, the session will always be answered."""
    )
    days_of_activity = fields.Integer(
        'Days of activity',
        group_operator="max",
        readonly=True,
        help="Number of days since the first session of the operator")
    is_anonymous = fields.Integer('Is visitor anonymous', readonly=True)
    country_id = fields.Many2one('res.country',
                                 'Country of the visitor',
                                 readonly=True)
    is_happy = fields.Integer('Visitor is Happy', readonly=True)
    rating = fields.Integer('Rating', group_operator="avg", readonly=True)
    # TODO DBE : Use Selection field - Need : Pie chart must show labels, not keys.
    rating_text = fields.Char('Satisfaction Rate', readonly=True)
    is_unrated = fields.Integer('Session not rated', readonly=True)
    partner_id = fields.Many2one('res.partner', 'Operator', readonly=True)

    def init(self):
        # Note : start_date_hour must be remove when the read_group will allow grouping on the hour of a datetime. Don't forget to change the view !
        tools.drop_view_if_exists(self.env.cr, 'im_livechat_report_channel')
        self.env.cr.execute("""
            CREATE OR REPLACE VIEW im_livechat_report_channel AS (
                SELECT
                    C.id as id,
                    C.uuid as uuid,
                    C.id as channel_id,
                    C.name as channel_name,
                    CONCAT(L.name, ' / ', C.id) as technical_name,
                    C.livechat_channel_id as livechat_channel_id,
                    C.create_date as start_date,
                    to_char(date_trunc('hour', C.create_date), 'YYYY-MM-DD HH24:MI:SS') as start_date_hour,
                    to_char(date_trunc('hour', C.create_date), 'HH24') as start_hour,
                    extract(dow from  C.create_date) as day_number, 
                    EXTRACT('epoch' FROM MAX(M.create_date) - MIN(M.create_date)) AS duration,
                    EXTRACT('epoch' FROM MIN(MO.create_date) - MIN(M.create_date)) AS time_to_answer,
                    count(distinct C.livechat_operator_id) as nbr_speaker,
                    count(distinct M.id) as nbr_message,
                    CASE 
                        WHEN EXISTS (select distinct M.author_id FROM mail_message M, mail_message_mail_channel_rel R 
                                        WHERE M.author_id=C.livechat_operator_id AND R.mail_channel_id = C.id 
                                        AND R.mail_message_id = M.id and C.livechat_operator_id = M.author_id)
                        THEN 0
                        ELSE 1
                    END as is_without_answer,
                    (DATE_PART('day', date_trunc('day', now()) - date_trunc('day', C.create_date)) + 1) as days_of_activity,
                    CASE
                        WHEN C.anonymous_name IS NULL THEN 0
                        ELSE 1
                    END as is_anonymous,
                    C.country_id,
                    CASE 
                        WHEN rate.rating = 10 THEN 1
                        ELSE 0
                    END as is_happy,
                    Rate.rating as rating,
                    CASE
                        WHEN Rate.rating = 1 THEN 'Unhappy'
                        WHEN Rate.rating = 10 THEN 'Happy'
                        WHEN Rate.rating = 5 THEN 'Neutral'
                        ELSE null
                    END as rating_text,
                    CASE 
                        WHEN rate.rating > 0 THEN 0
                        ELSE 1
                    END as is_unrated,
                    C.livechat_operator_id as partner_id
                FROM mail_channel C
                    JOIN mail_message_mail_channel_rel R ON (C.id = R.mail_channel_id)
                    JOIN mail_message M ON (M.id = R.mail_message_id)
                    JOIN im_livechat_channel L ON (L.id = C.livechat_channel_id)
                    LEFT JOIN mail_message MO ON (R.mail_message_id = MO.id AND MO.author_id = C.livechat_operator_id)
                    LEFT JOIN rating_rating Rate ON (Rate.res_id = C.id and Rate.res_model = 'mail.channel' and Rate.parent_res_model = 'im_livechat.channel')
                    WHERE C.livechat_operator_id is not null
                GROUP BY C.livechat_operator_id, C.id, C.name, C.livechat_channel_id, L.name, C.create_date, C.uuid, Rate.rating
            )
        """)
Пример #14
0
class StockProductionLot(models.Model):
    _inherit = 'stock.production.lot'

    life_date = fields.Datetime(
        string='End of Life Date',
        help=
        'This is the date on which the goods with this Serial Number may become dangerous and must not be consumed.'
    )
    use_date = fields.Datetime(
        string='Best before Date',
        help=
        'This is the date on which the goods with this Serial Number start deteriorating, without being dangerous yet.'
    )
    removal_date = fields.Datetime(
        string='Removal Date',
        help=
        'This is the date on which the goods with this Serial Number should be removed from the stock. This date will be used in FEFO removal strategy.'
    )
    alert_date = fields.Datetime(
        string='Alert Date',
        help=
        'Date to determine the expired lots and serial numbers using the filter "Expiration Alerts".'
    )
    product_expiry_alert = fields.Boolean(
        compute='_compute_product_expiry_alert',
        help="The Alert Date has been reached.")
    product_expiry_reminded = fields.Boolean(string="Expiry has been reminded")

    @api.depends('alert_date')
    def _compute_product_expiry_alert(self):
        current_date = fields.Datetime.now()
        lots = self.filtered(lambda l: l.alert_date)
        for lot in lots:
            lot.product_expiry_alert = lot.alert_date <= current_date
        (self - lots).product_expiry_alert = False

    def _get_dates(self, product_id=None):
        """Returns dates based on number of days configured in current lot's product."""
        mapped_fields = {
            'life_date': 'life_time',
            'use_date': 'use_time',
            'removal_date': 'removal_time',
            'alert_date': 'alert_time'
        }
        res = dict.fromkeys(mapped_fields, False)
        product = self.env['product.product'].browse(
            product_id) or self.product_id
        if product:
            for field in mapped_fields:
                duration = getattr(product, mapped_fields[field])
                if duration:
                    date = datetime.datetime.now() + datetime.timedelta(
                        days=duration)
                    res[field] = fields.Datetime.to_string(date)
        return res

    # Assign dates according to products data
    @api.model
    def create(self, vals):
        dates = self._get_dates(
            vals.get('product_id')
            or self.env.context.get('default_product_id'))
        for d in dates:
            if not vals.get(d):
                vals[d] = dates[d]
        return super(StockProductionLot, self).create(vals)

    @api.onchange('product_id')
    def _onchange_product(self):
        dates_dict = self._get_dates()
        for field, value in dates_dict.items():
            setattr(self, field, value)

    @api.model
    def _alert_date_exceeded(self):
        """Log an activity on internally stored lots whose alert_date has been reached.

        No further activity will be generated on lots whose alert_date
        has already been reached (even if the alert_date is changed).
        """
        alert_lots = self.env['stock.production.lot'].search([
            ('alert_date', '<=', fields.Date.today()),
            ('product_expiry_reminded', '=', False)
        ])

        lot_stock_quants = self.env['stock.quant'].search([
            ('lot_id', 'in', alert_lots.ids), ('quantity', '>', 0),
            ('location_id.usage', '=', 'internal')
        ])
        alert_lots = lot_stock_quants.mapped('lot_id')

        for lot in alert_lots:
            lot.activity_schedule(
                'product_expiry.mail_activity_type_alert_date_reached',
                user_id=lot.product_id.responsible_id.id or SUPERUSER_ID,
                note=_(
                    "The alert date has been reached for this lot/serial number"
                ))
        alert_lots.write({'product_expiry_reminded': True})
Пример #15
0
class SaleOrderInherited(models.Model):
    _inherit = 'sale.order'

    pi_type = fields.Selection([('LOCAL', 'LOCAL'), ('L/C', 'L/C'),
                                ('L/C-DELAY', 'L/C Delay')],
                               'PI Type',
                               required=True)

    commodity = fields.Char(string='Commodity')
    method_of_payment = fields.Many2one('method_of_payment.model',
                                        string='Method of Payment')
    terms_note = fields.Many2one('terms_conditions.model',
                                 'Terms and conditions')
    # erc_no =  fields.Char(string='ERC NO.', default=lambda self: self._default_erc_no())
    erc_no = fields.Char(string='ERC NO.')
    # bin_no =  fields.Char(string='BIN', default=lambda self: self._default_bin_no())
    bin_no = fields.Char(string='BIN')
    agent_code = fields.Char(string='Agent Code')
    bags_of_packing = fields.Char(string='Packing', default='50')
    country_of_origin = fields.Many2one('country_origin.model',
                                        string='Country Of Origin')

    lc_num_id = fields.Many2one('lc_informations.model', string='L/C No')
    lc_num = fields.Char(string='L/C No')
    lc_created_date = fields.Char(string='L/C Created Date')
    amend_no = fields.Char(string='Amend No/Date')
    org_beneficiary_bank_name = fields.Many2one(
        'beneficiary_bank_names_branch_address.model',
        string='Beneficiary Bank Name',
        required=True)
    beneficiary_bank_name2 = fields.Char(string='beneficiary_bank_name2')
    beneficiary_bank_branch = fields.Char(string='Beneficiary Bank Branch')
    beneficiary_bank_address = fields.Text(string='Beneficiary Bank Address')
    swift_code = fields.Char(string='Swift Code')
    account_number = fields.Char(string='Account Number')
    validity_date = fields.Datetime(string='Validity Date', required=True)
    beneficiary_bank_branch = fields.Char(string='Beneficiary Bank Branch')

    time_of_delivery = fields.Char(string='Time of Delivery',
                                   default='Within 30 days from the date.')
    reimbursement = fields.Many2one('reimbursement.model',
                                    string='Reimbursement')
    posted_by = fields.Char(string='Posted By', default='SMS')
    hs_code = fields.Char(string='H.S Code', default='5203.00.00')
    remarks = fields.Char(string='Remarks')
    product_type = fields.Many2one('product_type.model', string='Type')
    terms_of_delivery = fields.Many2one('terms_of_delivery.model',
                                        string='Terms of Delivery')
    # place_of_delivery_addr =  fields.Char(string='Delivery Factory Address', default=lambda self: self._default_place_of_delivery_addr())
    place_of_delivery_addr = fields.Char(string='Delivery Factory Address')
    signature = fields.Many2one('signature_upload.model', string='Signature')
    signature_image = fields.Binary('Signarute_image',
                                    help="Select signature image here")
    unity_of_mesure2 = fields.Many2one('product.uom', string='Unit Of Mesure')

    # quantity_total = fields.Monetary(string='Total Quantity', store=True, readonly=True, compute='_amount_all', track_visibility='always')
    # benificiary_name = fields.Many2one('res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('sale.order'))

    # 'quantity_total': fields.function(_amount_all_wrapper, string='Total Quantity', type='integer', store=True,multi='sums', help="The total quantity."),

    def onchange_lc_num(self, lc_num_id):
        lc_num_id = lc_num_id
        service_obj = self.env['lc_informations.model'].browse(lc_num_id)
        lc_num = service_obj.name
        lc_created_date = service_obj.created_date
        if lc_num:
            res = {
                'value': {
                    'lc_num': lc_num,
                    'lc_created_date': lc_created_date,
                }
            }
        else:
            res = {}
        return res

    @api.onchange('org_beneficiary_bank_name')
    def onchange_org_beneficiary_bank_name(self):
        org_beneficiary_bank_name_id = self.org_beneficiary_bank_name.id
        # raise UserError(_(org_beneficiary_bank_name_id))
        service_obj = self.env[
            'beneficiary_bank_names_branch_address.model'].browse(
                org_beneficiary_bank_name_id)
        b_name = service_obj.bank_name
        b_branch = service_obj.bank_branch
        b_addr = service_obj.bank_address
        swift_code = service_obj.s_code

        if b_name:
            res = {
                'value': {
                    'beneficiary_bank_name2': b_name,
                    'beneficiary_bank_branch': b_branch,
                    'beneficiary_bank_address': b_addr,
                    'swift_code': swift_code
                }
            }
        else:
            res = {}

        return res

    def onchange_signature(self, signature):
        signature_id = signature

        if signature_id:
            service_obj = self.env['signature_upload.model'].browse(
                signature_id)
            name = service_obj.my_binary_field_name

            res = {'value': {'signature_image': name}}
        else:
            res = {'value': {'signature_image': ''}}

        return res
Пример #16
0
class ActivityReport(models.Model):
    """ CRM Lead Analysis """

    _name = "crm.activity.report"
    _auto = False
    _description = "CRM Activity Analysis"
    _rec_name = 'id'

    date = fields.Datetime('Completion Date', readonly=True)
    lead_create_date = fields.Datetime('Creation Date', readonly=True)
    date_conversion = fields.Datetime('Conversion Date', readonly=True)
    date_deadline = fields.Date('Expected Closing', readonly=True)
    date_closed = fields.Datetime('Closed Date', readonly=True)
    author_id = fields.Many2one('res.partner', 'Assigned To', readonly=True)
    user_id = fields.Many2one('res.users', 'Salesperson', readonly=True)
    team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True)
    lead_id = fields.Many2one('crm.lead', "Opportunity", readonly=True)
    body = fields.Html('Activity Description', readonly=True)
    subtype_id = fields.Many2one('mail.message.subtype', 'Subtype', readonly=True)
    mail_activity_type_id = fields.Many2one('mail.activity.type', 'Activity Type', readonly=True)
    country_id = fields.Many2one('res.country', 'Country', readonly=True)
    company_id = fields.Many2one('res.company', 'Company', readonly=True)
    stage_id = fields.Many2one('crm.stage', 'Stage', readonly=True)
    partner_id = fields.Many2one('res.partner', 'Customer', readonly=True)
    lead_type = fields.Selection(
        string='Type',
        selection=[('lead', 'Lead'), ('opportunity', 'Opportunity')],
        help="Type is used to separate Leads and Opportunities")
    active = fields.Boolean('Active', readonly=True)

    def _select(self):
        return """
            SELECT
                m.id,
                l.create_date AS lead_create_date,
                l.date_conversion,
                l.date_deadline,
                l.date_closed,
                m.subtype_id,
                m.mail_activity_type_id,
                m.author_id,
                m.date,
                m.body,
                l.id as lead_id,
                l.user_id,
                l.team_id,
                l.country_id,
                l.company_id,
                l.stage_id,
                l.partner_id,
                l.type as lead_type,
                l.active
        """

    def _from(self):
        return """
            FROM mail_message AS m
        """

    def _join(self):
        return """
            JOIN crm_lead AS l ON m.res_id = l.id
        """

    def _where(self):
        disccusion_subtype = self.env.ref('mail.mt_comment')
        return """
            WHERE
                m.model = 'crm.lead' AND (m.mail_activity_type_id IS NOT NULL OR m.subtype_id = %s)
        """ % (disccusion_subtype.id,)

    def init(self):
        tools.drop_view_if_exists(self._cr, self._table)
        self._cr.execute("""
            CREATE OR REPLACE VIEW %s AS (
                %s
                %s
                %s
                %s
            )
        """ % (self._table, self._select(), self._from(), self._join(), self._where())
        )
Пример #17
0
class OpFaculty(models.Model):
    _name = "op.faculty"
    _description = "OpenEagleEdu Faculty"
    _inherit = "mail.thread"
    _inherits = {"res.partner": "partner_id"}
    partner_id = fields.Many2one('res.partner',
                                 'Partner',
                                 required=True,
                                 ondelete="cascade")
    first_name = fields.Char('First Name', size=128, translate=True)
    middle_name = fields.Char('Middle Name', size=128)
    last_name = fields.Char('Last Name', size=128, required=True)
    birth_date = fields.Date('Birth Date', required=True)
    blood_group = fields.Selection([('A+', 'A+ve'), ('B+', 'B+ve'),
                                    ('O+', 'O+ve'), ('AB+', 'AB+ve'),
                                    ('A-', 'A-ve'), ('B-', 'B-ve'),
                                    ('O-', 'O-ve'), ('AB-', 'AB-ve')],
                                   string='Blood Group')
    gender = fields.Selection([('male', 'Male'), ('female', 'Female')],
                              'Gender',
                              required=True)
    nationality = fields.Many2one('res.country', 'Nationality')
    emergency_contact = fields.Many2one('res.partner', 'Emergency Contact')
    visa_info = fields.Char('Visa Info', size=64)
    id_number = fields.Char('ID Card Number', size=64)
    login = fields.Char('Login',
                        related='partner_id.user_id.login',
                        readonly=1)
    last_login = fields.Datetime('Latest Connection',
                                 readonly=1,
                                 related='partner_id.user_id.login_date')
    faculty_subject_ids = fields.Many2many('op.subject',
                                           string='Subject(s)',
                                           track_visibility='onchange')
    emp_id = fields.Many2one('hr.employee', 'HR Employee')

    @api.constrains('birth_date')
    def _check_birthdate(self):
        for record in self:
            if record.birth_date > fields.Date.today():
                raise ValidationError(
                    _("Birth Date can't be greater than current date!"))

    @api.onchange('first_name', 'middle_name', 'last_name')
    def _onchange_name(self):
        if not self.middle_name:
            self.name = str(self.first_name) + " " + str(self.last_name)
        else:
            self.name = str(self.first_name) + " " + str(
                self.middle_name) + " " + str(self.last_name)

    def create_employee(self):
        for record in self:
            vals = {
                'name': record.name,
                'country_id': record.nationality.id,
                'gender': record.gender,
                'address_home_id': record.partner_id.id
            }
            emp_id = self.env['hr.employee'].create(vals)
            record.write({'emp_id': emp_id.id})
            record.partner_id.write({'partner_share': True, 'employee': True})

    @api.model
    def get_import_templates(self):
        return [{
            'label': _('Import Template for Faculties'),
            'template': '/openeagleedu_core/static/xls/op_faculty.xls'
        }]
Пример #18
0
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'].with_context(
                company_id=self.env.user.company_id.id)._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 AND acc.company_id = %s
            GROUP BY partner.id
            HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator +
            ''' %s''',
            (account_type, self.env.user.company_id.id, 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:
            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 Count",
                                     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 determines the taxes/accounts used for this contact.",
        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='Partner 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,
                                    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

    @api.onchange('company_id')
    def _onchange_company_id(self):
        company = self.env['res.company']
        if self.company_id:
            company = self.company_id
        else:
            company = self.env.user.company_id
        return {
            'domain': {
                'property_account_position_id':
                [('company_id', 'in', [company.id, False])]
            }
        }

    def can_edit_vat(self):
        ''' Can't edit `vat` if there is (non draft) issued invoices. '''
        can_edit_vat = super(ResPartner, self).can_edit_vat()
        if not can_edit_vat:
            return can_edit_vat
        Invoice = self.env['account.invoice']
        has_invoice = Invoice.search(
            [('type', 'in', ['out_invoice', 'out_refund']),
             ('partner_id', 'child_of', self.commercial_partner_id.id),
             ('state', 'not in', ['draft', 'cancel'])],
            limit=1)
        return can_edit_vat and not (bool(has_invoice))
Пример #19
0
class StockMoveLine(models.Model):
    _name = "stock.move.line"
    _description = "Product Moves (Stock Move Line)"
    _rec_name = "product_id"
    _order = "result_package_id desc, id"

    picking_id = fields.Many2one(
        'stock.picking', 'Stock Picking', auto_join=True,
        help='The stock operation where the packing has been made')
    move_id = fields.Many2one(
        'stock.move', 'Stock Move',
        help="Change to a better name", index=True)
    product_id = fields.Many2one('product.product', 'Product', ondelete="cascade")
    product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure', required=True)
    product_qty = fields.Float(
        'Real Reserved Quantity', digits=0,
        compute='_compute_product_qty', inverse='_set_product_qty', store=True)
    product_uom_qty = fields.Float('Reserved', default=0.0, digits=dp.get_precision('Product Unit of Measure'), required=True)
    qty_done = fields.Float('Done', default=0.0, digits=dp.get_precision('Product Unit of Measure'), copy=False)
    package_id = fields.Many2one('stock.quant.package', 'Source Package', ondelete='restrict')
    package_level_id = fields.Many2one('stock.package_level', 'Package Level')
    lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number')
    lot_name = fields.Char('Lot/Serial Number Name')
    result_package_id = fields.Many2one(
        'stock.quant.package', 'Destination Package',
        ondelete='restrict', required=False,
        help="If set, the operations are packed into this package")
    date = fields.Datetime('Date', default=fields.Datetime.now, required=True)
    owner_id = fields.Many2one('res.partner', 'Owner', help="Owner of the quants")
    location_id = fields.Many2one('stock.location', 'From', required=True)
    location_dest_id = fields.Many2one('stock.location', 'To', required=True)
    lots_visible = fields.Boolean(compute='_compute_lots_visible')
    picking_type_use_create_lots = fields.Boolean(related='picking_id.picking_type_id.use_create_lots', readonly=True)
    picking_type_use_existing_lots = fields.Boolean(related='picking_id.picking_type_id.use_existing_lots', readonly=True)
    state = fields.Selection(related='move_id.state', store=True, related_sudo=False, readonly=False)
    is_initial_demand_editable = fields.Boolean(related='move_id.is_initial_demand_editable', readonly=False)
    is_locked = fields.Boolean(related='move_id.is_locked', default=True, readonly=True)
    consume_line_ids = fields.Many2many('stock.move.line', 'stock_move_line_consume_rel', 'consume_line_id', 'produce_line_id', help="Technical link to see who consumed what. ")
    produce_line_ids = fields.Many2many('stock.move.line', 'stock_move_line_consume_rel', 'produce_line_id', 'consume_line_id', help="Technical link to see which line was produced with this. ")
    reference = fields.Char(related='move_id.reference', store=True, related_sudo=False, readonly=False)
    tracking = fields.Selection(related='product_id.tracking', readonly=True)
    picking_type_entire_packs = fields.Boolean(related='picking_id.picking_type_id.show_entire_packs', readonly=True)

    @api.one
    @api.depends('picking_id.picking_type_id', 'product_id.tracking')
    def _compute_lots_visible(self):
        picking = self.picking_id
        if picking.picking_type_id and self.product_id.tracking != 'none':  # TDE FIXME: not sure correctly migrated
            self.lots_visible = picking.picking_type_id.use_existing_lots or picking.picking_type_id.use_create_lots
        else:
            self.lots_visible = self.product_id.tracking != 'none'

    @api.one
    @api.depends('product_id', 'product_uom_id', 'product_uom_qty')
    def _compute_product_qty(self):
        self.product_qty = self.product_uom_id._compute_quantity(self.product_uom_qty, self.product_id.uom_id, rounding_method='HALF-UP')

    @api.constrains('lot_id', 'product_id')
    def _check_lot_product(self):
        for line in self:
            if line.lot_id and line.product_id != line.lot_id.product_id:
                raise ValidationError(_('This lot %s is incompatible with this product %s' % (line.lot_id.name, line.product_id.display_name)))

    @api.one
    def _set_product_qty(self):
        """ The meaning of product_qty field changed lately and is now a functional field computing the quantity
        in the default product UoM. This code has been added to raise an error if a write is made given a value
        for `product_qty`, where the same write should set the `product_uom_qty` field instead, in order to
        detect errors. """
        raise UserError(_('The requested operation cannot be processed because of a programming error setting the `product_qty` field instead of the `product_uom_qty`.'))

    @api.constrains('product_uom_qty')
    def check_reserved_done_quantity(self):
        for move_line in self:
            if move_line.state == 'done' and not float_is_zero(move_line.product_uom_qty, precision_digits=self.env['decimal.precision'].precision_get('Product Unit of Measure')):
                raise ValidationError(_('A done move line should never have a reserved quantity.'))

    @api.onchange('product_id', 'product_uom_id')
    def onchange_product_id(self):
        if self.product_id:
            self.lots_visible = self.product_id.tracking != 'none'
            if not self.product_uom_id or self.product_uom_id.category_id != self.product_id.uom_id.category_id:
                if self.move_id.product_uom:
                    self.product_uom_id = self.move_id.product_uom.id
                else:
                    self.product_uom_id = self.product_id.uom_id.id
            res = {'domain': {'product_uom_id': [('category_id', '=', self.product_uom_id.category_id.id)]}}
        else:
            res = {'domain': {'product_uom_id': []}}
        return res

    @api.onchange('lot_name', 'lot_id')
    def onchange_serial_number(self):
        """ When the user is encoding a move line for a tracked product, we apply some logic to
        help him. This includes:
            - automatically switch `qty_done` to 1.0
            - warn if he has already encoded `lot_name` in another move line
        """
        res = {}
        if self.product_id.tracking == 'serial':
            if not self.qty_done:
                self.qty_done = 1

            message = None
            if self.lot_name or self.lot_id:
                move_lines_to_check = self._get_similar_move_lines() - self
                if self.lot_name:
                    counter = Counter([line.lot_name for line in move_lines_to_check])
                    if counter.get(self.lot_name) and counter[self.lot_name] > 1:
                        message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
                elif self.lot_id:
                    counter = Counter([line.lot_id.id for line in move_lines_to_check])
                    if counter.get(self.lot_id.id) and counter[self.lot_id.id] > 1:
                        message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')

            if message:
                res['warning'] = {'title': _('Warning'), 'message': message}
        return res

    @api.onchange('qty_done')
    def _onchange_qty_done(self):
        """ When the user is encoding a move line for a tracked product, we apply some logic to
        help him. This onchange will warn him if he set `qty_done` to a non-supported value.
        """
        res = {}
        if self.qty_done and self.product_id.tracking == 'serial':
            if float_compare(self.qty_done, 1.0, precision_rounding=self.product_id.uom_id.rounding) != 0:
                message = _('You can only process 1.0 %s of products with unique serial number.') % self.product_id.uom_id.name
                res['warning'] = {'title': _('Warning'), 'message': message}
        return res

    @api.constrains('qty_done')
    def _check_positive_qty_done(self):
        if any([ml.qty_done < 0 for ml in self]):
            raise ValidationError(_('You can not enter negative quantities.'))

    def _get_similar_move_lines(self):
        self.ensure_one()
        lines = self.env['stock.move.line']
        picking_id = self.move_id.picking_id if self.move_id else self.picking_id
        if picking_id:
            lines |= picking_id.move_line_ids.filtered(lambda ml: ml.product_id == self.product_id and (ml.lot_id or ml.lot_name))
        return lines

    @api.model_create_multi
    def create(self, vals_list):
        for vals in vals_list:

            # If the move line is directly create on the picking view.
            # If this picking is already done we should generate an
            # associated done move.
            if 'picking_id' in vals and not vals.get('move_id'):
                picking = self.env['stock.picking'].browse(vals['picking_id'])
                if picking.state == 'done':
                    product = self.env['product.product'].browse(vals['product_id'])
                    new_move = self.env['stock.move'].create({
                        'name': _('New Move:') + product.display_name,
                        'product_id': product.id,
                        'product_uom_qty': 'qty_done' in vals and vals['qty_done'] or 0,
                        'product_uom': vals['product_uom_id'],
                        'location_id': 'location_id' in vals and vals['location_id'] or picking.location_id.id,
                        'location_dest_id': 'location_dest_id' in vals and vals['location_dest_id'] or picking.location_dest_id.id,
                        'state': 'done',
                        'additional': True,
                        'picking_id': picking.id,
                    })
                    vals['move_id'] = new_move.id

        mls = super(StockMoveLine, self).create(vals_list)

        for ml in mls:
            if ml.state == 'done':
                if 'qty_done' in vals:
                    ml.move_id.product_uom_qty = ml.move_id.quantity_done
                if ml.product_id.type == 'product':
                    Quant = self.env['stock.quant']
                    quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id,rounding_method='HALF-UP')
                    in_date = None
                    available_qty, in_date = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
                    if available_qty < 0 and ml.lot_id:
                        # see if we can compensate the negative quants with some untracked quants
                        untracked_qty = Quant._get_available_quantity(ml.product_id, ml.location_id, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
                        if untracked_qty:
                            taken_from_untracked_qty = min(untracked_qty, abs(quantity))
                            Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
                            Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
                    Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date)
                next_moves = ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel'))
                next_moves._do_unreserve()
                next_moves._action_assign()

        return mls

    def write(self, vals):
        """ Through the interface, we allow users to change the charateristics of a move line. If a
        quantity has been reserved for this move line, we impact the reservation directly to free
        the old quants and allocate the new ones.
        """
        if self.env.context.get('bypass_reservation_update'):
            return super(StockMoveLine, self).write(vals)

        Quant = self.env['stock.quant']
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        # We forbid to change the reserved quantity in the interace, but it is needed in the
        # case of stock.move's split.
        # TODO Move me in the update
        if 'product_uom_qty' in vals:
            for ml in self.filtered(lambda m: m.state in ('partially_available', 'assigned') and m.product_id.type == 'product'):
                if not ml.location_id.should_bypass_reservation():
                    qty_to_decrease = ml.product_qty - ml.product_uom_id._compute_quantity(vals['product_uom_qty'], ml.product_id.uom_id, rounding_method='HALF-UP')
                    try:
                        Quant._update_reserved_quantity(ml.product_id, ml.location_id, -qty_to_decrease, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
                    except UserError:
                        if ml.lot_id:
                            Quant._update_reserved_quantity(ml.product_id, ml.location_id, -qty_to_decrease, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
                        else:
                            raise

        triggers = [
            ('location_id', 'stock.location'),
            ('location_dest_id', 'stock.location'),
            ('lot_id', 'stock.production.lot'),
            ('package_id', 'stock.quant.package'),
            ('result_package_id', 'stock.quant.package'),
            ('owner_id', 'res.partner')
        ]
        updates = {}
        for key, model in triggers:
            if key in vals:
                updates[key] = self.env[model].browse(vals[key])

        if updates:
            for ml in self.filtered(lambda ml: ml.state in ['partially_available', 'assigned'] and ml.product_id.type == 'product'):
                if not ml.location_id.should_bypass_reservation():
                    try:
                        Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
                    except UserError:
                        if ml.lot_id:
                            Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
                        else:
                            raise

                if not updates.get('location_id', ml.location_id).should_bypass_reservation():
                    new_product_qty = 0
                    try:
                        q = Quant._update_reserved_quantity(ml.product_id, updates.get('location_id', ml.location_id), ml.product_qty, lot_id=updates.get('lot_id', ml.lot_id),
                                                             package_id=updates.get('package_id', ml.package_id), owner_id=updates.get('owner_id', ml.owner_id), strict=True)
                        new_product_qty = sum([x[1] for x in q])
                    except UserError:
                        if updates.get('lot_id'):
                            # If we were not able to reserve on tracked quants, we can use untracked ones.
                            try:
                                q = Quant._update_reserved_quantity(ml.product_id, updates.get('location_id', ml.location_id), ml.product_qty, lot_id=False,
                                                                     package_id=updates.get('package_id', ml.package_id), owner_id=updates.get('owner_id', ml.owner_id), strict=True)
                                new_product_qty = sum([x[1] for x in q])
                            except UserError:
                                pass
                    if new_product_qty != ml.product_qty:
                        new_product_uom_qty = ml.product_id.uom_id._compute_quantity(new_product_qty, ml.product_uom_id, rounding_method='HALF-UP')
                        ml.with_context(bypass_reservation_update=True).product_uom_qty = new_product_uom_qty

        # When editing a done move line, the reserved availability of a potential chained move is impacted. Take care of running again `_action_assign` on the concerned moves.
        next_moves = self.env['stock.move']
        if updates or 'qty_done' in vals:
            mls = self.filtered(lambda ml: ml.move_id.state == 'done' and ml.product_id.type == 'product')
            if not updates:  # we can skip those where qty_done is already good up to UoM rounding
                mls = mls.filtered(lambda ml: not float_is_zero(ml.qty_done - vals['qty_done'], precision_rounding=ml.product_uom_id.rounding))
            for ml in mls:
                # undo the original move line
                qty_done_orig = ml.move_id.product_uom._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
                in_date = Quant._update_available_quantity(ml.product_id, ml.location_dest_id, -qty_done_orig, lot_id=ml.lot_id,
                                                      package_id=ml.result_package_id, owner_id=ml.owner_id)[1]
                Quant._update_available_quantity(ml.product_id, ml.location_id, qty_done_orig, lot_id=ml.lot_id,
                                                      package_id=ml.package_id, owner_id=ml.owner_id, in_date=in_date)

                # move what's been actually done
                product_id = ml.product_id
                location_id = updates.get('location_id', ml.location_id)
                location_dest_id = updates.get('location_dest_id', ml.location_dest_id)
                qty_done = vals.get('qty_done', ml.qty_done)
                lot_id = updates.get('lot_id', ml.lot_id)
                package_id = updates.get('package_id', ml.package_id)
                result_package_id = updates.get('result_package_id', ml.result_package_id)
                owner_id = updates.get('owner_id', ml.owner_id)
                quantity = ml.move_id.product_uom._compute_quantity(qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
                if not location_id.should_bypass_reservation():
                    ml._free_reservation(product_id, location_id, quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
                if not float_is_zero(quantity, precision_digits=precision):
                    available_qty, in_date = Quant._update_available_quantity(product_id, location_id, -quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
                    if available_qty < 0 and lot_id:
                        # see if we can compensate the negative quants with some untracked quants
                        untracked_qty = Quant._get_available_quantity(product_id, location_id, lot_id=False, package_id=package_id, owner_id=owner_id, strict=True)
                        if untracked_qty:
                            taken_from_untracked_qty = min(untracked_qty, abs(available_qty))
                            Quant._update_available_quantity(product_id, location_id, -taken_from_untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id)
                            Quant._update_available_quantity(product_id, location_id, taken_from_untracked_qty, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
                            if not location_id.should_bypass_reservation():
                                ml._free_reservation(ml.product_id, location_id, untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id)
                    Quant._update_available_quantity(product_id, location_dest_id, quantity, lot_id=lot_id, package_id=result_package_id, owner_id=owner_id, in_date=in_date)

                # Unreserve and reserve following move in order to have the real reserved quantity on move_line.
                next_moves |= ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel'))

                # Log a note
                if ml.picking_id:
                    ml._log_message(ml.picking_id, ml, 'stock.track_move_template', vals)

        res = super(StockMoveLine, self).write(vals)

        # Update scrap object linked to move_lines to the new quantity.
        if 'qty_done' in vals:
            for move in self.mapped('move_id'):
                if move.scrapped:
                    move.scrap_ids.write({'scrap_qty': move.quantity_done})

        # As stock_account values according to a move's `product_uom_qty`, we consider that any
        # done stock move should have its `quantity_done` equals to its `product_uom_qty`, and
        # this is what move's `action_done` will do. So, we replicate the behavior here.
        if updates or 'qty_done' in vals:
            moves = self.filtered(lambda ml: ml.move_id.state == 'done').mapped('move_id')
            for move in moves:
                move.product_uom_qty = move.quantity_done
        next_moves._do_unreserve()
        next_moves._action_assign()
        return res

    def unlink(self):
        precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
        for ml in self:
            if ml.state in ('done', 'cancel'):
                raise UserError(_('You can not delete product moves if the picking is done. You can only correct the done quantities.'))
            # Unlinking a move line should unreserve.
            if ml.product_id.type == 'product' and not ml.location_id.should_bypass_reservation() and not float_is_zero(ml.product_qty, precision_digits=precision):
                try:
                    self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
                except UserError:
                    if ml.lot_id:
                        self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
                    else:
                        raise
        moves = self.mapped('move_id')
        res = super(StockMoveLine, self).unlink()
        if moves:
            moves._recompute_state()
        return res

    def _action_done(self):
        """ This method is called during a move's `action_done`. It'll actually move a quant from
        the source location to the destination location, and unreserve if needed in the source
        location.

        This method is intended to be called on all the move lines of a move. This method is not
        intended to be called when editing a `done` move (that's what the override of `write` here
        is done.
        """
        Quant = self.env['stock.quant']

        # First, we loop over all the move lines to do a preliminary check: `qty_done` should not
        # be negative and, according to the presence of a picking type or a linked inventory
        # adjustment, enforce some rules on the `lot_id` field. If `qty_done` is null, we unlink
        # the line. It is mandatory in order to free the reservation and correctly apply
        # `action_done` on the next move lines.
        ml_to_delete = self.env['stock.move.line']
        for ml in self:
            # Check here if `ml.qty_done` respects the rounding of `ml.product_uom_id`.
            uom_qty = float_round(ml.qty_done, precision_rounding=ml.product_uom_id.rounding, rounding_method='HALF-UP')
            precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
            qty_done = float_round(ml.qty_done, precision_digits=precision_digits, rounding_method='HALF-UP')
            if float_compare(uom_qty, qty_done, precision_digits=precision_digits) != 0:
                raise UserError(_('The quantity done for the product "%s" doesn\'t respect the rounding precision \
                                  defined on the unit of measure "%s". Please change the quantity done or the \
                                  rounding precision of your unit of measure.') % (ml.product_id.display_name, ml.product_uom_id.name))

            qty_done_float_compared = float_compare(ml.qty_done, 0, precision_rounding=ml.product_uom_id.rounding)
            if qty_done_float_compared > 0:
                if ml.product_id.tracking != 'none':
                    picking_type_id = ml.move_id.picking_type_id
                    if picking_type_id:
                        if picking_type_id.use_create_lots:
                            # If a picking type is linked, we may have to create a production lot on
                            # the fly before assigning it to the move line if the user checked both
                            # `use_create_lots` and `use_existing_lots`.
                            if ml.lot_name and not ml.lot_id:
                                lot = self.env['stock.production.lot'].create(
                                    {'name': ml.lot_name, 'product_id': ml.product_id.id}
                                )
                                ml.write({'lot_id': lot.id})
                        elif not picking_type_id.use_create_lots and not picking_type_id.use_existing_lots:
                            # If the user disabled both `use_create_lots` and `use_existing_lots`
                            # checkboxes on the picking type, he's allowed to enter tracked
                            # products without a `lot_id`.
                            continue
                    elif ml.move_id.inventory_id:
                        # If an inventory adjustment is linked, the user is allowed to enter
                        # tracked products without a `lot_id`.
                        continue

                    if not ml.lot_id:
                        raise UserError(_('You need to supply a Lot/Serial number for product %s.') % ml.product_id.display_name)
            elif qty_done_float_compared < 0:
                raise UserError(_('No negative quantities allowed'))
            else:
                ml_to_delete |= ml
        ml_to_delete.unlink()

        # Now, we can actually move the quant.
        done_ml = self.env['stock.move.line']
        for ml in self - ml_to_delete:
            if ml.product_id.type == 'product':
                rounding = ml.product_uom_id.rounding

                # if this move line is force assigned, unreserve elsewhere if needed
                if not ml.location_id.should_bypass_reservation() and float_compare(ml.qty_done, ml.product_qty, precision_rounding=rounding) > 0:
                    extra_qty = ml.qty_done - ml.product_qty
                    ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, ml_to_ignore=done_ml)
                # unreserve what's been reserved
                if not ml.location_id.should_bypass_reservation() and ml.product_id.type == 'product' and ml.product_qty:
                    try:
                        Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
                    except UserError:
                        Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)

                # move what's been actually done
                quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
                available_qty, in_date = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
                if available_qty < 0 and ml.lot_id:
                    # see if we can compensate the negative quants with some untracked quants
                    untracked_qty = Quant._get_available_quantity(ml.product_id, ml.location_id, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
                    if untracked_qty:
                        taken_from_untracked_qty = min(untracked_qty, abs(quantity))
                        Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
                        Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
                Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date)
            done_ml |= ml
        # Reset the reserved quantity as we just moved it to the destination location.
        (self - ml_to_delete).with_context(bypass_reservation_update=True).write({
            'product_uom_qty': 0.00,
            'date': fields.Datetime.now(),
        })

    def _log_message(self, record, move, template, vals):
        data = vals.copy()
        if 'lot_id' in vals and vals['lot_id'] != move.lot_id.id:
            data['lot_name'] = self.env['stock.production.lot'].browse(vals.get('lot_id')).name
        if 'location_id' in vals:
            data['location_name'] = self.env['stock.location'].browse(vals.get('location_id')).name
        if 'location_dest_id' in vals:
            data['location_dest_name'] = self.env['stock.location'].browse(vals.get('location_dest_id')).name
        if 'package_id' in vals and vals['package_id'] != move.package_id.id:
            data['package_name'] = self.env['stock.quant.package'].browse(vals.get('package_id')).name
        if 'package_result_id' in vals and vals['package_result_id'] != move.package_result_id.id:
            data['result_package_name'] = self.env['stock.quant.package'].browse(vals.get('result_package_id')).name
        if 'owner_id' in vals and vals['owner_id'] != move.owner_id.id:
            data['owner_name'] = self.env['res.partner'].browse(vals.get('owner_id')).name
        record.message_post_with_view(template, values={'move': move, 'vals': dict(vals, **data)}, subtype_id=self.env.ref('mail.mt_note').id)

    def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, ml_to_ignore=None):
        """ When editing a done move line or validating one with some forced quantities, it is
        possible to impact quants that were not reserved. It is therefore necessary to edit or
        unlink the move lines that reserved a quantity now unavailable.

        :param ml_to_ignore: recordset of `stock.move.line` that should NOT be unreserved
        """
        self.ensure_one()

        if ml_to_ignore is None:
            ml_to_ignore = self.env['stock.move.line']
        ml_to_ignore |= self

        # Check the available quantity, with the `strict` kw set to `True`. If the available
        # quantity is greather than the quantity now unavailable, there is nothing to do.
        available_quantity = self.env['stock.quant']._get_available_quantity(
            product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True
        )
        if quantity > available_quantity:
            # We now have to find the move lines that reserved our now unavailable quantity. We
            # take care to exclude ourselves and the move lines were work had already been done.
            outdated_move_lines_domain = [
                ('move_id.state', 'not in', ['done', 'cancel']),
                ('product_id', '=', product_id.id),
                ('lot_id', '=', lot_id.id if lot_id else False),
                ('location_id', '=', location_id.id),
                ('owner_id', '=', owner_id.id if owner_id else False),
                ('package_id', '=', package_id.id if package_id else False),
                ('product_qty', '>', 0.0),
                ('id', 'not in', ml_to_ignore.ids),
            ]
            current_picking_first = lambda cand: cand.picking_id != self.move_id.picking_id
            outdated_candidates = self.env['stock.move.line'].search(outdated_move_lines_domain).sorted(current_picking_first)

            # As the move's state is not computed over the move lines, we'll have to manually
            # recompute the moves which we adapted their lines.
            move_to_recompute_state = self.env['stock.move']

            rounding = self.product_uom_id.rounding
            for candidate in outdated_candidates:
                if float_compare(candidate.product_qty, quantity, precision_rounding=rounding) <= 0:
                    quantity -= candidate.product_qty
                    move_to_recompute_state |= candidate.move_id
                    if candidate.qty_done:
                        candidate.product_uom_qty = 0.0
                    else:
                        candidate.unlink()
                    if float_is_zero(quantity, precision_rounding=rounding):
                        break
                else:
                    # split this move line and assign the new part to our extra move
                    quantity_split = float_round(
                        candidate.product_qty - quantity,
                        precision_rounding=self.product_uom_id.rounding,
                        rounding_method='UP')
                    candidate.product_uom_qty = self.product_id.uom_id._compute_quantity(quantity_split, candidate.product_uom_id, rounding_method='HALF-UP')
                    move_to_recompute_state |= candidate.move_id
                    break
            move_to_recompute_state._recompute_state()
Пример #20
0
class Property(models.Model):
    _name = 'ir.property'
    _description = 'Company Property'

    name = fields.Char(index=True)
    res_id = fields.Char(
        string='Resource',
        index=True,
        help="If not set, acts as a default value for new resources",
    )
    company_id = fields.Many2one('res.company', string='Company', index=True)
    fields_id = fields.Many2one('ir.model.fields',
                                string='Field',
                                ondelete='cascade',
                                required=True,
                                index=True)
    value_float = fields.Float()
    value_integer = fields.Integer()
    value_text = fields.Text()  # will contain (char, text)
    value_binary = fields.Binary(attachment=False)
    value_reference = fields.Char()
    value_datetime = fields.Datetime()
    type = fields.Selection([
        ('char', 'Char'),
        ('float', 'Float'),
        ('boolean', 'Boolean'),
        ('integer', 'Integer'),
        ('text', 'Text'),
        ('binary', 'Binary'),
        ('many2one', 'Many2One'),
        ('date', 'Date'),
        ('datetime', 'DateTime'),
        ('selection', 'Selection'),
    ],
                            required=True,
                            default='many2one',
                            index=True)

    def _update_values(self, values):
        if 'value' not in values:
            return values
        value = values.pop('value')

        prop = None
        type_ = values.get('type')
        if not type_:
            if self:
                prop = self[0]
                type_ = prop.type
            else:
                type_ = self._fields['type'].default(self)

        field = TYPE2FIELD.get(type_)
        if not field:
            raise UserError(_('Invalid type'))

        if field == 'value_reference':
            if not value:
                value = False
            elif isinstance(value, models.BaseModel):
                value = '%s,%d' % (value._name, value.id)
            elif isinstance(value, int):
                field_id = values.get('fields_id')
                if not field_id:
                    if not prop:
                        raise ValueError()
                    field_id = prop.fields_id
                else:
                    field_id = self.env['ir.model.fields'].browse(field_id)

                value = '%s,%d' % (field_id.sudo().relation, value)

        values[field] = value
        return values

    def write(self, values):
        # if any of the records we're writing on has a res_id=False *or*
        # we're writing a res_id=False on any record
        default_set = False
        if self._ids:
            self.env.cr.execute(
                'SELECT EXISTS (SELECT 1 FROM ir_property WHERE id in %s AND res_id IS NULL)',
                [self._ids])
            default_set = self.env.cr.rowcount == 1 or any(
                v.get('res_id') is False for v in values)
        r = super(Property, self).write(self._update_values(values))
        if default_set:
            # DLE P44: test `test_27_company_dependent`
            # Easy solution, need to flush write when changing a property.
            # Maybe it would be better to be able to compute all impacted cache value and update those instead
            # Then clear_caches must be removed as well.
            self.flush()
            self.clear_caches()
        return r

    @api.model_create_multi
    def create(self, vals_list):
        vals_list = [self._update_values(vals) for vals in vals_list]
        created_default = any(not v.get('res_id') for v in vals_list)
        r = super(Property, self).create(vals_list)
        if created_default:
            # DLE P44: test `test_27_company_dependent`
            self.flush()
            self.clear_caches()
        return r

    def unlink(self):
        default_deleted = False
        if self._ids:
            self.env.cr.execute(
                'SELECT EXISTS (SELECT 1 FROM ir_property WHERE id in %s)',
                [self._ids])
            default_deleted = self.env.cr.rowcount == 1
        r = super().unlink()
        if default_deleted:
            self.clear_caches()
        return r

    def get_by_record(self):
        self.ensure_one()
        if self.type in ('char', 'text', 'selection'):
            return self.value_text
        elif self.type == 'float':
            return self.value_float
        elif self.type == 'boolean':
            return bool(self.value_integer)
        elif self.type == 'integer':
            return self.value_integer
        elif self.type == 'binary':
            return self.value_binary
        elif self.type == 'many2one':
            if not self.value_reference:
                return False
            model, resource_id = self.value_reference.split(',')
            return self.env[model].browse(int(resource_id)).exists()
        elif self.type == 'datetime':
            return self.value_datetime
        elif self.type == 'date':
            if not self.value_datetime:
                return False
            return fields.Date.to_string(
                fields.Datetime.from_string(self.value_datetime))
        return False

    @api.model
    def get(self, name, model, res_id=False):
        if not res_id:
            t, v = self._get_default_property(name, model)
            if not v or t != 'many2one':
                return v
            return self.env[v[0]].browse(v[1])

        p = self._get_property(name, model, res_id=res_id)
        if p:
            return p.get_by_record()
        return False

    # only cache Property.get(res_id=False) as that's
    # sub-optimally.
    COMPANY_KEY = "self.env.context.get('force_company') or self.env.company.id"

    @ormcache(COMPANY_KEY, 'name', 'model')
    def _get_default_property(self, name, model):
        prop = self._get_property(name, model, res_id=False)
        if not prop:
            return None, False
        v = prop.get_by_record()
        if prop.type != 'many2one':
            return prop.type, v
        return 'many2one', v and (v._name, v.id)

    def _get_property(self, name, model, res_id):
        domain = self._get_domain(name, model)
        if domain is not None:
            domain = [('res_id', '=', res_id)] + domain
            #make the search with company_id asc to make sure that properties specific to a company are given first
            return self.search(domain, limit=1, order='company_id')
        return self.browse(())

    def _get_domain(self, prop_name, model):
        self._cr.execute(
            "SELECT id FROM ir_model_fields WHERE name=%s AND model=%s",
            (prop_name, model))
        res = self._cr.fetchone()
        if not res:
            return None
        company_id = self._context.get('force_company') or self.env.company.id
        return [('fields_id', '=', res[0]),
                ('company_id', 'in', [company_id, False])]

    @api.model
    def get_multi(self, name, model, ids):
        """ Read the property field `name` for the records of model `model` with
            the given `ids`, and return a dictionary mapping `ids` to their
            corresponding value.
        """
        if not ids:
            return {}

        field = self.env[model]._fields[name]
        field_id = self.env['ir.model.fields']._get(model, name).id
        company_id = (self._context.get('force_company')
                      or self.env.company.id)

        if field.type == 'many2one':
            comodel = self.env[field.comodel_name]
            model_pos = len(model) + 2
            value_pos = len(comodel._name) + 2
            # retrieve values: both p.res_id and p.value_reference are formatted
            # as "<rec._name>,<rec.id>"; the purpose of the LEFT JOIN is to
            # return the value id if it exists, NULL otherwise
            query = """
                SELECT substr(p.res_id, %s)::integer, r.id
                FROM ir_property p
                LEFT JOIN {} r ON substr(p.value_reference, %s)::integer=r.id
                WHERE p.fields_id=%s
                    AND (p.company_id=%s OR p.company_id IS NULL)
                    AND (p.res_id IN %s OR p.res_id IS NULL)
                ORDER BY p.company_id NULLS FIRST
            """.format(comodel._table)
            params = [model_pos, value_pos, field_id, company_id]
            clean = comodel.browse

        elif field.type in TYPE2FIELD:
            model_pos = len(model) + 2
            # retrieve values: p.res_id is formatted as "<rec._name>,<rec.id>"
            query = """
                SELECT substr(p.res_id, %s)::integer, p.{}
                FROM ir_property p
                WHERE p.fields_id=%s
                    AND (p.company_id=%s OR p.company_id IS NULL)
                    AND (p.res_id IN %s OR p.res_id IS NULL)
                ORDER BY p.company_id NULLS FIRST
            """.format(TYPE2FIELD[field.type])
            params = [model_pos, field_id, company_id]
            clean = TYPE2CLEAN[field.type]

        else:
            return dict.fromkeys(ids, False)

        # retrieve values
        cr = self.env.cr
        result = {}
        refs = {"%s,%s" % (model, id) for id in ids}
        for sub_refs in cr.split_for_in_conditions(refs):
            cr.execute(query, params + [sub_refs])
            result.update(cr.fetchall())

        # remove default value, add missing values, and format them
        default = result.pop(None, None)
        for id in ids:
            result[id] = clean(result.get(id, default))
        return result

    @api.model
    def set_multi(self, name, model, values, default_value=None):
        """ Assign the property field `name` for the records of model `model`
            with `values` (dictionary mapping record ids to their value).
            If the value for a given record is the same as the default
            value, the property entry will not be stored, to avoid bloating
            the database.
            If `default_value` is provided, that value will be used instead
            of the computed default value, to determine whether the value
            for a record should be stored or not.
        """
        def clean(value):
            return value.id if isinstance(value, models.BaseModel) else value

        if not values:
            return

        if default_value is None:
            domain = self._get_domain(name, model)
            if domain is None:
                raise Exception()
            # retrieve the default value for the field
            default_value = clean(self.get(name, model))

        # retrieve the properties corresponding to the given record ids
        self._cr.execute(
            "SELECT id FROM ir_model_fields WHERE name=%s AND model=%s",
            (name, model))
        field_id = self._cr.fetchone()[0]
        company_id = self.env.context.get(
            'force_company') or self.env.company.id
        refs = {('%s,%s' % (model, id)): id for id in values}
        props = self.search([
            ('fields_id', '=', field_id),
            ('company_id', '=', company_id),
            ('res_id', 'in', list(refs)),
        ])

        # modify existing properties
        for prop in props:
            id = refs.pop(prop.res_id)
            value = clean(values[id])
            if value == default_value:
                # avoid prop.unlink(), as it clears the record cache that can
                # contain the value of other properties to set on record!
                prop.check_access_rights('unlink')
                prop.check_access_rule('unlink')
                self._cr.execute("DELETE FROM ir_property WHERE id=%s",
                                 [prop.id])
            elif value != clean(prop.get_by_record()):
                prop.write({'value': value})

        # create new properties for records that do not have one yet
        vals_list = []
        for ref, id in refs.items():
            value = clean(values[id])
            if value != default_value:
                vals_list.append({
                    'fields_id': field_id,
                    'company_id': company_id,
                    'res_id': ref,
                    'name': name,
                    'value': value,
                    'type': self.env[model]._fields[name].type,
                })
        self.create(vals_list)

    @api.model
    def search_multi(self, name, model, operator, value):
        """ Return a domain for the records that match the given condition. """
        default_matches = False
        include_zero = False

        field = self.env[model]._fields[name]
        if field.type == 'many2one':
            comodel = field.comodel_name

            def makeref(value):
                return value and '%s,%s' % (comodel, value)

            if operator == "=":
                value = makeref(value)
                # if searching properties not set, search those not in those set
                if value is False:
                    default_matches = True
            elif operator in ('!=', '<=', '<', '>', '>='):
                value = makeref(value)
            elif operator in ('in', 'not in'):
                value = [makeref(v) for v in value]
            elif operator in ('=like', '=ilike', 'like', 'not like', 'ilike',
                              'not ilike'):
                # most probably inefficient... but correct
                target = self.env[comodel]
                target_names = target.name_search(value,
                                                  operator=operator,
                                                  limit=None)
                target_ids = [n[0] for n in target_names]
                operator, value = 'in', [makeref(v) for v in target_ids]
        elif field.type in ('integer', 'float'):
            # No record is created in ir.property if the field's type is float or integer with a value
            # equal to 0. Then to match with the records that are linked to a property field equal to 0,
            # the negation of the operator must be taken  to compute the goods and the domain returned
            # to match the searched records is just the opposite.
            if value == 0 and operator == '=':
                operator = '!='
                include_zero = True
            elif value <= 0 and operator == '>=':
                operator = '<'
                include_zero = True
            elif value < 0 and operator == '>':
                operator = '<='
                include_zero = True
            elif value >= 0 and operator == '<=':
                operator = '>'
                include_zero = True
            elif value > 0 and operator == '<':
                operator = '>='
                include_zero = True

        # retrieve the properties that match the condition
        domain = self._get_domain(name, model)
        if domain is None:
            raise Exception()
        props = self.search(domain +
                            [(TYPE2FIELD[field.type], operator, value)])

        # retrieve the records corresponding to the properties that match
        good_ids = []
        for prop in props:
            if prop.res_id:
                res_model, res_id = prop.res_id.split(',')
                good_ids.append(int(res_id))
            else:
                default_matches = True

        if include_zero:
            return [('id', 'not in', good_ids)]
        elif default_matches:
            # exclude all records with a property that does not match
            all_ids = []
            props = self.search(domain + [('res_id', '!=', False)])
            for prop in props:
                res_model, res_id = prop.res_id.split(',')
                all_ids.append(int(res_id))
            bad_ids = list(set(all_ids) - set(good_ids))
            return [('id', 'not in', bad_ids)]
        else:
            return [('id', 'in', good_ids)]
Пример #21
0
class PosSaleReport(models.Model):
    _name = "report.all.channels.sales"
    _description = "Sales by Channel (All in One)"
    _auto = False

    name = fields.Char('Order Reference', readonly=True)
    partner_id = fields.Many2one('res.partner', 'Partner', readonly=True)
    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 readonly=True)
    product_tmpl_id = fields.Many2one('product.template',
                                      'Product Template',
                                      readonly=True)
    date_order = fields.Datetime(string='Date Order', readonly=True)
    user_id = fields.Many2one('res.users', 'Salesperson', readonly=True)
    categ_id = fields.Many2one('product.category',
                               'Product Category',
                               readonly=True)
    company_id = fields.Many2one('res.company', 'Company', readonly=True)
    price_total = fields.Float('Total', readonly=True)
    pricelist_id = fields.Many2one('product.pricelist',
                                   'Pricelist',
                                   readonly=True)
    country_id = fields.Many2one('res.country',
                                 'Partner Country',
                                 readonly=True)
    price_subtotal = fields.Float(string='Price Subtotal', readonly=True)
    product_qty = fields.Float('Product Quantity', readonly=True)
    analytic_account_id = fields.Many2one('account.analytic.account',
                                          'Analytic Account',
                                          readonly=True)
    team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True)

    def _so(self):
        so_str = """
                SELECT sol.id AS id,
                    so.name AS name,
                    so.partner_id AS partner_id,
                    sol.product_id AS product_id,
                    pro.product_tmpl_id AS product_tmpl_id,
                    so.date_order AS date_order,
                    so.user_id AS user_id,
                    pt.categ_id AS categ_id,
                    so.company_id AS company_id,
                    sol.price_total / CASE COALESCE(so.currency_rate, 0) WHEN 0 THEN 1.0 ELSE so.currency_rate END AS price_total,
                    so.pricelist_id AS pricelist_id,
                    rp.country_id AS country_id,
                    sol.price_subtotal / CASE COALESCE(so.currency_rate, 0) WHEN 0 THEN 1.0 ELSE so.currency_rate END AS price_subtotal,
                    (sol.product_uom_qty / u.factor * u2.factor) as product_qty,
                    so.analytic_account_id AS analytic_account_id,
                    so.team_id AS team_id

            FROM sale_order_line sol
                    JOIN sale_order so ON (sol.order_id = so.id)
                    LEFT JOIN product_product pro ON (sol.product_id = pro.id)
                    JOIN res_partner rp ON (so.partner_id = rp.id)
                    LEFT JOIN product_template pt ON (pro.product_tmpl_id = pt.id)
                    LEFT JOIN product_pricelist pp ON (so.pricelist_id = pp.id)
                    LEFT JOIN uom_uom u on (u.id=sol.product_uom)
                    LEFT JOIN uom_uom u2 on (u2.id=pt.uom_id)
            WHERE so.state in ('sale','done')
        """
        return so_str

    def _from(self):
        return """(%s)""" % (self._so())

    def get_main_request(self):
        request = """
            CREATE or REPLACE VIEW %s AS
                SELECT id AS id,
                    name,
                    partner_id,
                    product_id,
                    product_tmpl_id,
                    date_order,
                    user_id,
                    categ_id,
                    company_id,
                    price_total,
                    pricelist_id,
                    analytic_account_id,
                    country_id,
                    team_id,
                    price_subtotal,
                    product_qty
                FROM %s
                AS foo""" % (self._table, self._from())
        return request

    def init(self):
        tools.drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute(self.get_main_request())
Пример #22
0
class EagleeduAssigningClass(models.Model):
    _name = 'eagleedu.assigning.class'
    _description = 'Assign the Students to Class'
    # _inherit = ['mail.thread']
    # _rec_name = 'class_assign_name'
    name = fields.Char('Class Assign Register', compute='get_class_assign_name')
    keep_roll_no=fields.Boolean("keep Roll No")
    class_id = fields.Many2one('eagleedu.standard_class', string='Class')
    student_list = fields.One2many('eagleedu.student.list', 'connect_id', string="Students")
    admitted_class = fields.Many2one('eagleedu.class.division', string="Admitted Class" )
    assigned_by = fields.Many2one('res.users', string='Assigned By', default=lambda self: self.env.uid)
    state = fields.Selection([('draft', 'Draft'), ('done', 'Done')],
                             string='State', required=True, default='draft', track_visibility='onchange')


    assign_date = fields.Datetime(string='Asigned Date', default=datetime.today())

    #assign_date = fields.Date(string='Assigned Date',default=fields.date.today())


    def get_class_assign_name(self):
        for rec in self:
            rec.name = str(rec.admitted_class.name) + '(Assign on ' + str(rec.assign_date) +')'
            #rec.name = rec.admitted_class.name #+ '(assigned on '+ rec.assign_date +')'

    @api.model
    def assigning_class(self):
        max_roll = self.env['eagleedu.class.history'].search([('class_id','=',self.admitted_class.id)], order='roll_no desc', limit=1)
        if max_roll.roll_no:
            next_roll = max_roll.roll_no
        else:
            next_roll = 0

        for rec in self:

            if not self.student_list:
                raise ValidationError(_('No Student Lines'))
            com_sub = self.env['eagleedu.syllabus'].search(
                            [('class_id', '=', rec.class_id.id),
                             ('academic_year', '=', rec.admitted_class.academic_year_id.id),
                             ('divisional','=',False),
                             ('selection_type', '=', 'compulsory')])
            elect_sub=self.env['eagleedu.syllabus'].search(
                            [('class_id', '=', rec.class_id.id),
                             ('academic_year', '=', rec.admitted_class.academic_year_id.id),
                             ('divisional','=',True),
                             ('division_id','=',rec.admitted_class.id),
                             ('selection_type', '=', 'compulsory')])
            com_subjects = [] # compulsory Subject List
            el_subjects = [] # Elective Subject List
            for sub in com_sub:
                com_subjects.append(sub.id)
            for sub in elect_sub:
                el_subjects.append(sub.id)
            for line in self.student_list:
                st=self.env['eagleedu.student'].search([('id','=',line.student_id.id)])
                st.class_id = rec.admitted_class.id
                if self.keep_roll_no != True:
                    next_roll = next_roll + 1
                    line.roll_no=next_roll
                st.roll_no = line.roll_no


                # create student history

                self.env['eagleedu.class.history'].create({'academic_year_id': rec.admitted_class.academic_year_id.id,
                                                            'class_id': rec.admitted_class.id,
                                                            'student_id': line.student_id.id,
                                                            'roll_no': line.roll_no,
                                                            'compulsory_subjects': [(6, 0,com_subjects)],
                                                            'selective_subjects': [(6, 0, el_subjects)]
                                                            })


            self.write({
                'state': 'done'
                })

    def unlink(self):
        """Return warning if the Record is in done state"""
        for rec in self:
            if rec.state == 'done':
                raise ValidationError(_("Cannot delete Record in Done state"))

    def get_student_list(self):
        """returns the list of students applied to join the selected class"""
        for rec in self:
            for line in rec.student_list:
                line.unlink()
            # TODO apply filter not to get student assigned previously
            students = self.env['eagleedu.student'].search([
                ('class_id', '=', rec.admitted_class.id),('assigned', '=', False)])
            if not students:
                raise ValidationError(_('No Students Available.. !'))
            values = []
            for stud in students:
                stud_line = {
                    'class_id': rec.class_id.id,
                    'student_id': stud.id,
                    'connect_id': rec.id,
                    'roll_no': stud.application_id.roll_no
                }
                stud.assigned=True
                values.append(stud_line)
            for line in values:
                rec.student_line = self.env['eagleedu.student.list'].create(line)
Пример #23
0
class HolidaysAllocation(models.Model):
    """ Allocation Requests Access specifications: similar to leave requests """
    _name = "hr.leave.allocation"
    _description = "Leaves Allocation"
    _inherit = ['mail.thread', 'mail.activity.mixin']
    _mail_post_access = 'read'

    def _default_employee(self):
        return self.env.context.get('default_employee_id') or self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)

    def _default_holiday_status_id(self):
        if self.user_has_groups('hr_holidays.group_hr_holidays_user'):
            domain = [('valid', '=', True)]
        else:
            domain = [('valid', '=', True), ('allocation_type', 'in', ('no', 'fixed_allocation'))]
        return self.env['hr.leave.type'].search(domain, limit=1)

    name = fields.Char('Description')
    state = fields.Selection([
        ('draft', 'To Submit'),
        ('cancel', 'Cancelled'),
        ('confirm', 'To Approve'),
        ('refuse', 'Refused'),
        ('validate1', 'Second Approval'),
        ('validate', 'Approved')
        ], string='Status', readonly=True, track_visibility='onchange', copy=False, default='confirm',
        help="The status is set to 'To Submit', when a leave request is created." +
        "\nThe status is 'To Approve', when leave request is confirmed by user." +
        "\nThe status is 'Refused', when leave request is refused by manager." +
        "\nThe status is 'Approved', when leave request is approved by manager.")
    date_from = fields.Datetime(
        'Start Date', readonly=True, index=True, copy=False,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange')
    date_to = fields.Datetime(
        'End Date', readonly=True, copy=False,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange')
    holiday_status_id = fields.Many2one(
        "hr.leave.type", string="Leave Type", required=True, readonly=True,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
        domain=[('valid', '=', True)], default=_default_holiday_status_id)
    employee_id = fields.Many2one(
        'hr.employee', string='Employee', index=True, readonly=True,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, track_visibility='onchange')
    notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    # duration
    number_of_days = fields.Float(
        'Number of Days', track_visibility='onchange',
        help='Duration in days. Reference field to use when necessary.')
    number_of_days_display = fields.Float(
        'Duration (days)', compute='_compute_number_of_days_display',
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
        help="UX field allowing to see and modify the allocation duration, computed in days.")
    number_of_hours_display = fields.Float(
        'Duration (hours)', compute='_compute_number_of_hours_display',
        help="UX field allowing to see and modify the allocation duration, computed in hours.")
    duration_display = fields.Char('Allocated (Days/Hours)', compute='_compute_duration_display',
        help="Field allowing to see the allocation duration in days or hours depending on the type_request_unit")
    # details
    parent_id = fields.Many2one('hr.leave.allocation', string='Parent')
    linked_request_ids = fields.One2many('hr.leave.allocation', 'parent_id', string='Linked Requests')
    first_approver_id = fields.Many2one(
        'hr.employee', string='First Approval', readonly=True, copy=False,
        help='This area is automatically filled by the user who validate the leave', oldname='manager_id')
    second_approver_id = fields.Many2one(
        'hr.employee', string='Second Approval', readonly=True, copy=False, oldname='manager_id2',
        help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)')
    validation_type = fields.Selection('Validation Type', related='holiday_status_id.validation_type', readonly=True)
    can_reset = fields.Boolean('Can reset', compute='_compute_can_reset')
    can_approve = fields.Boolean('Can Approve', compute='_compute_can_approve')
    type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True)
    # mode
    holiday_type = fields.Selection([
        ('employee', 'By Employee'),
        ('company', 'By Company'),
        ('department', 'By Department'),
        ('category', 'By Employee Tag')],
        string='Allocation Mode', readonly=True, required=True, default='employee',
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]},
        help="Allow to create requests in batchs:\n- By Employee: for a specific employee"
             "\n- By Company: all employees of the specified company"
             "\n- By Department: all employees of the specified department"
             "\n- By Employee Tag: all employees of the specific employee group category")
    mode_company_id = fields.Many2one(
        'res.company', string='Company', readonly=True,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    department_id = fields.Many2one(
        'hr.department', string='Department', readonly=True,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    category_id = fields.Many2one(
        'hr.employee.category', string='Employee Tag', readonly=True,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    # accrual configuration
    accrual = fields.Boolean(
        "Accrual", readonly=True,
        states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    accrual_limit = fields.Integer('Balance limit', default=0, help="Maximum of allocation for accrual; 0 means no maximum.")
    number_per_interval = fields.Float("Number of unit per interval", readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=1)
    interval_number = fields.Integer("Number of unit between two intervals", readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=1)
    unit_per_interval = fields.Selection([
        ('hours', 'Hour(s)'),
        ('days', 'Day(s)')
        ], string="Unit of time added at each interval", default='hours', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    interval_unit = fields.Selection([
        ('weeks', 'Week(s)'),
        ('months', 'Month(s)'),
        ('years', 'Year(s)')
        ], string="Unit of time between two intervals", default='weeks', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]})
    nextcall = fields.Date("Date of the next accrual allocation", default=False, readonly=True)

    _sql_constraints = [
        ('type_value',
         "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or "
         "(holiday_type='category' AND category_id IS NOT NULL) or "
         "(holiday_type='department' AND department_id IS NOT NULL) or "
         "(holiday_type='company' AND mode_company_id IS NOT NULL))",
         "The employee, department, company or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
        ('duration_check', "CHECK ( number_of_days >= 0 )", "The number of days must be greater than 0."),
        ('number_per_interval_check', "CHECK(number_per_interval > 0)", "The number per interval should be greater than 0"),
        ('interval_number_check', "CHECK(interval_number > 0)", "The interval number should be greater than 0"),
    ]

    @api.model
    def _update_accrual(self):
        """
            Method called by the cron task in order to increment the number_of_days when
            necessary.
        """
        today = fields.Date.from_string(fields.Date.today())

        holidays = self.search([('accrual', '=', True), ('state', '=', 'validate'), ('holiday_type', '=', 'employee'),
                                '|', ('date_to', '=', False), ('date_to', '>', fields.Datetime.now()),
                                '|', ('nextcall', '=', False), ('nextcall', '<=', today)])

        for holiday in holidays:
            values = {}

            delta = relativedelta(days=0)

            if holiday.interval_unit == 'weeks':
                delta = relativedelta(weeks=holiday.interval_number)
            if holiday.interval_unit == 'months':
                delta = relativedelta(months=holiday.interval_number)
            if holiday.interval_unit == 'years':
                delta = relativedelta(years=holiday.interval_number)

            values['nextcall'] = (holiday.nextcall if holiday.nextcall else today) + delta

            period_start = datetime.combine(today, time(0, 0, 0)) - delta
            period_end = datetime.combine(today, time(0, 0, 0))

            # We have to check when the employee has been created
            # in order to not allocate him/her too much leaves
            start_date = holiday.employee_id._get_date_start_work()
            # If employee is created after the period, we cancel the computation
            if period_end <= start_date:
                holiday.write(values)
                continue

            # If employee created during the period, taking the date at which he has been created
            if period_start <= start_date:
                period_start = start_date

            worked = holiday.employee_id.get_work_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
            left = holiday.employee_id.get_leave_days_data(period_start, period_end, domain=[('holiday_id.holiday_status_id.unpaid', '=', True), ('time_type', '=', 'leave')])['days']
            prorata = worked / (left + worked) if worked else 0

            days_to_give = holiday.number_per_interval
            if holiday.unit_per_interval == 'hours':
                # As we encode everything in days in the database we need to convert
                # the number of hours into days for this we use the
                # mean number of hours set on the employee's calendar
                days_to_give = days_to_give / (holiday.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY)

            values['number_of_days'] = holiday.number_of_days + days_to_give * prorata
            if holiday.accrual_limit > 0:
                values['number_of_days'] = min(values['number_of_days'], holiday.accrual_limit)

            holiday.write(values)

    @api.multi
    @api.depends('number_of_days')
    def _compute_number_of_days_display(self):
        for allocation in self:
            allocation.number_of_days_display = allocation.number_of_days

    @api.multi
    @api.depends('number_of_days', 'employee_id')
    def _compute_number_of_hours_display(self):
        for allocation in self:
            if allocation.parent_id and allocation.parent_id.type_request_unit == "hour":
                allocation.number_of_hours_display = allocation.number_of_days * HOURS_PER_DAY
            else:
                allocation.number_of_hours_display = allocation.number_of_days * (allocation.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY)

    @api.multi
    @api.depends('number_of_hours_display', 'number_of_days_display')
    def _compute_duration_display(self):
        for allocation in self:
            allocation.duration_display = '%g %s' % (
                (float_round(allocation.number_of_hours_display, precision_digits=2)
                if allocation.type_request_unit == 'hour'
                else float_round(allocation.number_of_days_display, precision_digits=2)),
                _('hours') if allocation.type_request_unit == 'hour' else _('days'))

    @api.multi
    @api.depends('state', 'employee_id', 'department_id')
    def _compute_can_reset(self):
        for allocation in self:
            try:
                allocation._check_approval_update('draft')
            except (AccessError, UserError):
                allocation.can_reset = False
            else:
                allocation.can_reset = True

    @api.depends('state', 'employee_id', 'department_id')
    def _compute_can_approve(self):
        for allocation in self:
            try:
                if allocation.state == 'confirm' and allocation.holiday_status_id.validation_type == 'both':
                    allocation._check_approval_update('validate1')
                else:
                    allocation._check_approval_update('validate')
            except (AccessError, UserError):
                allocation.can_approve = False
            else:
                allocation.can_approve = True

    @api.multi
    @api.onchange('number_of_hours_display')
    def _onchange_number_of_hours_display(self):
        for allocation in self:
            allocation.number_of_days = allocation.number_of_hours_display / (allocation.employee_id.resource_calendar_id.hours_per_day or HOURS_PER_DAY)

    @api.multi
    @api.onchange('number_of_days_display')
    def _onchange_number_of_days_display(self):
        for allocation in self:
            allocation.number_of_days = allocation.number_of_days_display

    @api.onchange('holiday_type')
    def _onchange_type(self):
        if self.holiday_type == 'employee':
            if not self.employee_id:
                self.employee_id = self.env.user.employee_ids[:1].id
            self.mode_company_id = False
            self.category_id = False
        elif self.holiday_type == 'company':
            self.employee_id = False
            if not self.mode_company_id:
                self.mode_company_id = self.env.user.company_id.id
            self.category_id = False
        elif self.holiday_type == 'department':
            self.employee_id = False
            self.mode_company_id = False
            self.category_id = False
            if not self.department_id:
                self.department_id = self.env.user.employee_ids[:1].department_id.id
        elif self.holiday_type == 'category':
            self.employee_id = False
            self.mode_company_id = False
            self.department_id = False

    @api.onchange('employee_id')
    def _onchange_employee(self):
        if self.holiday_type == 'employee':
            self.department_id = self.employee_id.department_id

    @api.onchange('holiday_status_id')
    def _onchange_holiday_status_id(self):
        if self.holiday_status_id.validity_stop and self.date_to:
            new_date_to = datetime.combine(self.holiday_status_id.validity_stop, time.max)
            if new_date_to < self.date_to:
                self.date_to = new_date_to

        if self.accrual:
            self.number_of_days = 0

            if self.holiday_status_id.request_unit == 'hour':
                self.unit_per_interval = 'hours'
            else:
                self.unit_per_interval = 'days'
        else:
            self.interval_number = 1
            self.interval_unit = 'weeks'
            self.number_per_interval = 1
            self.unit_per_interval = 'hours'

    ####################################################
    # ORM Overrides methods
    ####################################################

    @api.multi
    def name_get(self):
        res = []
        for allocation in self:
            if allocation.holiday_type == 'company':
                target = allocation.mode_company_id.name
            elif allocation.holiday_type == 'department':
                target = allocation.department_id.name
            elif allocation.holiday_type == 'category':
                target = allocation.category_id.name
            else:
                target = allocation.employee_id.name

            res.append(
                (allocation.id,
                 _("Allocation of %s : %.2f %s to %s") %
                 (allocation.holiday_status_id.name,
                  allocation.number_of_hours_display if allocation.type_request_unit == 'hour' else allocation.number_of_days,
                  'hours' if allocation.type_request_unit == 'hour' else 'days',
                  target))
            )
        return res

    @api.multi
    def add_follower(self, employee_id):
        employee = self.env['hr.employee'].browse(employee_id)
        if employee.user_id:
            self.message_subscribe(partner_ids=employee.user_id.partner_id.ids)

    @api.multi
    @api.constrains('holiday_status_id')
    def _check_leave_type_validity(self):
        for allocation in self:
            if allocation.holiday_status_id.validity_start and allocation.holiday_status_id.validity_stop:
                vstart = allocation.holiday_status_id.validity_start
                vstop = allocation.holiday_status_id.validity_stop
                today = fields.Date.today()

                if vstart > today or vstop < today:
                    raise UserError(_('You can allocate %s only between %s and %s') % (allocation.holiday_status_id.display_name,
                                                                                  allocation.holiday_status_id.validity_start, allocation.holiday_status_id.validity_stop))

    @api.model
    def create(self, values):
        """ Override to avoid automatic logging of creation """
        if values.get('accrual', False):
            values['date_from'] = fields.Datetime.now()
        employee_id = values.get('employee_id', False)
        if not values.get('department_id'):
            values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
        holiday = super(HolidaysAllocation, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values)
        holiday.add_follower(employee_id)
        holiday.activity_update()
        return holiday

    @api.multi
    def write(self, values):
        employee_id = values.get('employee_id', False)
        if values.get('state'):
            self._check_approval_update(values['state'])
        result = super(HolidaysAllocation, self).write(values)
        self.add_follower(employee_id)
        return result

    @api.multi
    def unlink(self):
        for holiday in self.filtered(lambda holiday: holiday.state not in ['draft', 'cancel', 'confirm']):
            raise UserError(_('You cannot delete a leave which is in %s state.') % (holiday.state,))
        return super(HolidaysAllocation, self).unlink()

    @api.multi
    def copy_data(self, default=None):
        raise UserError(_('A leave cannot be duplicated.'))

    ####################################################
    # Business methods
    ####################################################

    @api.multi
    def _prepare_holiday_values(self, employee):
        self.ensure_one()
        values = {
            'name': self.name,
            'holiday_type': 'employee',
            'holiday_status_id': self.holiday_status_id.id,
            'notes': self.notes,
            'number_of_days': self.number_of_days,
            'parent_id': self.id,
            'employee_id': employee.id,
            'accrual': self.accrual,
            'date_to': self.date_to,
            'interval_unit': self.interval_unit,
            'interval_number': self.interval_number,
            'number_per_interval': self.number_per_interval,
            'unit_per_interval': self.unit_per_interval,
        }
        return values

    @api.multi
    def action_draft(self):
        for holiday in self:
            if holiday.state not in ['confirm', 'refuse']:
                raise UserError(_('Leave request state must be "Refused" or "To Approve" in order to reset to Draft.'))
            holiday.write({
                'state': 'draft',
                'first_approver_id': False,
                'second_approver_id': False,
            })
            linked_requests = holiday.mapped('linked_request_ids')
            for linked_request in linked_requests:
                linked_request.action_draft()
            linked_requests.unlink()
        self.activity_update()
        return True

    @api.multi
    def action_confirm(self):
        if self.filtered(lambda holiday: holiday.state != 'draft'):
            raise UserError(_('Leave request must be in Draft state ("To Submit") in order to confirm it.'))
        res = self.write({'state': 'confirm'})
        self.activity_update()
        return res

    @api.multi
    def action_approve(self):
        # if validation_type == 'both': this method is the first approval approval
        # if validation_type != 'both': this method calls action_validate() below
        if any(holiday.state != 'confirm' for holiday in self):
            raise UserError(_('Leave request must be confirmed ("To Approve") in order to approve it.'))

        current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)

        self.filtered(lambda hol: hol.validation_type == 'both').write({'state': 'validate1', 'first_approver_id': current_employee.id})
        self.filtered(lambda hol: not hol.validation_type == 'both').action_validate()
        self.activity_update()
        return True

    @api.multi
    def action_validate(self):
        current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
        for holiday in self:
            if holiday.state not in ['confirm', 'validate1']:
                raise UserError(_('Leave request must be confirmed in order to approve it.'))

            holiday.write({'state': 'validate'})
            if holiday.validation_type == 'both':
                holiday.write({'second_approver_id': current_employee.id})
            else:
                holiday.write({'first_approver_id': current_employee.id})

            holiday._action_validate_create_childs()
        self.activity_update()
        return True

    def _action_validate_create_childs(self):
        childs = self.env['hr.leave.allocation']
        if self.state == 'validate' and self.holiday_type in ['category', 'department', 'company']:
            if self.holiday_type == 'category':
                employees = self.category_id.employee_ids
            elif self.holiday_type == 'department':
                employees = self.department_id.member_ids
            else:
                employees = self.env['hr.employee'].search([('company_id', '=', self.mode_company_id.id)])

            for employee in employees:
                childs += self.with_context(
                    mail_notify_force_send=False,
                    mail_activity_automation_skip=True
                ).create(self._prepare_holiday_values(employee))
            # TODO is it necessary to interleave the calls?
            childs.action_approve()
            if childs and self.holiday_status_id.validation_type == 'both':
                childs.action_validate()
        return childs

    @api.multi
    def action_refuse(self):
        current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
        for holiday in self:
            if holiday.state not in ['confirm', 'validate', 'validate1']:
                raise UserError(_('Leave request must be confirmed or validated in order to refuse it.'))

            if holiday.state == 'validate1':
                holiday.write({'state': 'refuse', 'first_approver_id': current_employee.id})
            else:
                holiday.write({'state': 'refuse', 'second_approver_id': current_employee.id})
            # If a category that created several holidays, cancel all related
            holiday.linked_request_ids.action_refuse()
        self.activity_update()
        return True

    def _check_approval_update(self, state):
        """ Check if target state is achievable. """
        current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
        is_officer = self.env.user.has_group('hr_holidays.group_hr_holidays_user')
        is_manager = self.env.user.has_group('hr_holidays.group_hr_holidays_manager')
        for holiday in self:
            val_type = holiday.holiday_status_id.validation_type
            if state == 'confirm':
                continue

            if state == 'draft':
                if holiday.employee_id != current_employee and not is_manager:
                    raise UserError(_('Only a Leave Manager can reset other people leaves.'))
                continue

            if not is_officer:
                raise UserError(_('Only a Leave Officer or Manager can approve or refuse leave requests.'))

            if is_officer:
                # use ir.rule based first access check: department, members, ... (see security.xml)
                holiday.check_access_rule('write')

            if holiday.employee_id == current_employee and not is_manager:
                raise UserError(_('Only a Leave Manager can approve its own requests.'))

            if (state == 'validate1' and val_type == 'both') or (state == 'validate' and val_type == 'manager'):
                manager = holiday.employee_id.parent_id or holiday.employee_id.department_id.manager_id
                if (manager and manager != current_employee) and not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'):
                    raise UserError(_('You must be either %s\'s manager or Leave manager to approve this leave') % (holiday.employee_id.name))

            if state == 'validate' and val_type == 'both':
                if not self.env.user.has_group('hr_holidays.group_hr_holidays_manager'):
                    raise UserError(_('Only an Leave Manager can apply the second approval on leave requests.'))

    # ------------------------------------------------------------
    # Activity methods
    # ------------------------------------------------------------

    def _get_responsible_for_approval(self):
        if self.state == 'confirm' and self.employee_id.parent_id.user_id:
            return self.employee_id.parent_id.user_id
        elif self.department_id.manager_id.user_id:
            return self.department_id.manager_id.user_id
        return self.env['res.users']

    def activity_update(self):
        to_clean, to_do = self.env['hr.leave.allocation'], self.env['hr.leave.allocation']
        for allocation in self:
            if allocation.state == 'draft':
                to_clean |= allocation
            elif allocation.state == 'confirm':
                allocation.activity_schedule(
                    'hr_holidays.mail_act_leave_allocation_approval',
                    user_id=allocation.sudo()._get_responsible_for_approval().id or self.env.user.id)
            elif allocation.state == 'validate1':
                allocation.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval'])
                allocation.activity_schedule(
                    'hr_holidays.mail_act_leave_allocation_second_approval',
                    user_id=allocation.sudo()._get_responsible_for_approval().id or self.env.user.id)
            elif allocation.state == 'validate':
                to_do |= allocation
            elif allocation.state == 'refuse':
                to_clean |= allocation
        if to_clean:
            to_clean.activity_unlink(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval'])
        if to_do:
            to_do.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval'])

    ####################################################
    # Messaging methods
    ####################################################

    @api.multi
    def _track_subtype(self, init_values):
        if 'state' in init_values and self.state == 'validate':
            return 'hr_holidays.mt_leave_allocation_approved'
        elif 'state' in init_values and self.state == 'refuse':
            return 'hr_holidays.mt_leave_allocation_refused'
        return super(HolidaysAllocation, self)._track_subtype(init_values)

    @api.multi
    def _notify_get_groups(self, message, groups):
        """ Handle HR users and officers recipients that can validate or refuse holidays
        directly from email. """
        groups = super(HolidaysAllocation, self)._notify_get_groups(message, groups)

        self.ensure_one()
        hr_actions = []
        if self.state == 'confirm':
            app_action = self._notify_get_action_link('controller', controller='/allocation/validate')
            hr_actions += [{'url': app_action, 'title': _('Approve')}]
        if self.state in ['confirm', 'validate', 'validate1']:
            ref_action = self._notify_get_action_link('controller', controller='/allocation/refuse')
            hr_actions += [{'url': ref_action, 'title': _('Refuse')}]

        holiday_user_group_id = self.env.ref('hr_holidays.group_hr_holidays_user').id
        new_group = (
            'group_hr_holidays_user', lambda pdata: pdata['type'] == 'user' and holiday_user_group_id in pdata['groups'], {
                'actions': hr_actions,
            })

        return [new_group] + groups

    @api.multi
    def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None):
        # due to record rule can not allow to add follower and mention on validated leave so subscribe through sudo
        if self.state in ['validate', 'validate1']:
            self.check_access_rights('read')
            self.check_access_rule('read')
            return super(HolidaysAllocation, self.sudo()).message_subscribe(partner_ids=partner_ids, channel_ids=channel_ids, subtype_ids=subtype_ids)
        return super(HolidaysAllocation, self).message_subscribe(partner_ids=partner_ids, channel_ids=channel_ids, subtype_ids=subtype_ids)
Пример #24
0
class SurveyInvite(models.TransientModel):
    _name = 'survey.invite'
    _description = 'Survey Invitation Wizard'

    @api.model
    def _get_default_from(self):
        if self.env.user.email:
            return tools.formataddr((self.env.user.name, self.env.user.email))
        raise UserError(
            _("Unable to post message, please configure the sender's email address."
              ))

    @api.model
    def _get_default_author(self):
        return self.env.user.partner_id

    # composer content
    subject = fields.Char('Subject')
    body = fields.Html('Contents', default='', sanitize_style=True)
    attachment_ids = fields.Many2many(
        'ir.attachment',
        'survey_mail_compose_message_ir_attachments_rel',
        'wizard_id',
        'attachment_id',
        string='Attachments')
    template_id = fields.Many2one(
        'mail.template',
        'Use template',
        index=True,
        domain="[('model', '=', 'survey.user_input')]")
    # origin
    email_from = fields.Char('From',
                             default=_get_default_from,
                             help="Email address of the sender.")
    author_id = fields.Many2one('res.partner',
                                'Author',
                                index=True,
                                ondelete='set null',
                                default=_get_default_author,
                                help="Author of the message.")
    # recipients
    partner_ids = fields.Many2many('res.partner',
                                   'survey_invite_partner_ids',
                                   'invite_id',
                                   'partner_id',
                                   string='Recipients')
    existing_partner_ids = fields.Many2many(
        'res.partner',
        compute='_compute_existing_partner_ids',
        readonly=True,
        store=False)
    emails = fields.Text(
        string='Additional emails',
        help=
        "This list of emails of recipients will not be converted in contacts.\
        Emails must be separated by commas, semicolons or newline.")
    existing_emails = fields.Text('Existing emails',
                                  compute='_compute_existing_emails',
                                  readonly=True,
                                  store=False)
    existing_mode = fields.Selection([('new', 'New invite'),
                                      ('resend', 'Resend invite')],
                                     string='Handle existing',
                                     default='resend',
                                     required=True)
    existing_text = fields.Text('Resend Comment',
                                compute='_compute_existing_text',
                                readonly=True,
                                store=False)
    # technical info
    mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server')
    # survey
    survey_id = fields.Many2one('survey.survey',
                                string='Survey',
                                required=True)
    survey_url = fields.Char(related="survey_id.public_url", readonly=True)
    survey_access_mode = fields.Selection(related="survey_id.access_mode",
                                          readonly=True)
    survey_users_login_required = fields.Boolean(
        related="survey_id.users_login_required", readonly=True)
    deadline = fields.Datetime(string="Answer deadline")

    @api.depends('partner_ids', 'survey_id')
    def _compute_existing_partner_ids(self):
        existing_answers = self.survey_id.user_input_ids
        self.existing_partner_ids = existing_answers.mapped(
            'partner_id') & self.partner_ids

    @api.depends('emails', 'survey_id')
    def _compute_existing_emails(self):
        emails = list(set(emails_split.split(self.emails or "")))
        existing_emails = self.survey_id.mapped('user_input_ids.email')
        self.existing_emails = '\n'.join(email for email in emails
                                         if email in existing_emails)

    @api.depends('existing_partner_ids', 'existing_emails')
    def _compute_existing_text(self):
        existing_text = False
        if self.existing_partner_ids:
            existing_text = '%s: %s.' % (
                _('The following customers have already received an invite'),
                ', '.join(self.mapped('existing_partner_ids.name')))
        if self.existing_emails:
            existing_text = '%s\n' % existing_text if existing_text else ''
            existing_text += '%s: %s.' % (
                _('The following emails have already received an invite'),
                self.existing_emails)

        self.existing_text = existing_text

    @api.onchange('emails')
    def _onchange_emails(self):
        if self.emails and (self.survey_users_login_required
                            and not self.survey_id.users_can_signup):
            raise UserError(
                _('This survey does not allow external people to participate. You should create user accounts or update survey access mode accordingly.'
                  ))
        if not self.emails:
            return
        valid, error = [], []
        emails = list(set(emails_split.split(self.emails or "")))
        for email in emails:
            email_check = tools.email_split_and_format(email)
            if not email_check:
                error.append(email)
            else:
                valid.extend(email_check)
        if error:
            raise UserError(
                _("Some emails you just entered are incorrect: %s") %
                (', '.join(error)))
        self.emails = '\n'.join(valid)

    @api.onchange('survey_users_login_required')
    def _onchange_survey_users_login_required(self):
        if self.survey_users_login_required and not self.survey_id.users_can_signup:
            return {'domain': {'partner_ids': [('user_ids', '!=', False)]}}
        return {'domain': {'partner_ids': []}}

    @api.onchange('partner_ids')
    def _onchange_partner_ids(self):
        if self.survey_users_login_required and self.partner_ids:
            if not self.survey_id.users_can_signup:
                invalid_partners = self.env['res.partner'].search([
                    ('user_ids', '=', False),
                    ('id', 'in', self.partner_ids.ids)
                ])
                if invalid_partners:
                    raise UserError(
                        _('The following recipients have no user account: %s. You should create user accounts for them or allow external signup in configuration.'
                          % (','.join(invalid_partners.mapped('name')))))

    @api.onchange('template_id')
    def _onchange_template_id(self):
        """ UPDATE ME """
        if self.template_id:
            self.subject = self.template_id.subject
            self.body = self.template_id.body_html

    @api.model
    def create(self, values):
        if values.get('template_id') and not (values.get('body')
                                              or values.get('subject')):
            template = self.env['mail.template'].browse(values['template_id'])
            if not values.get('subject'):
                values['subject'] = template.subject
            if not values.get('body'):
                values['body'] = template.body_html
        return super(SurveyInvite, self).create(values)

    #------------------------------------------------------
    # Wizard validation and send
    #------------------------------------------------------

    def _prepare_answers(self, partners, emails):
        answers = self.env['survey.user_input']
        existing_answers = self.env['survey.user_input'].search([
            '&', ('survey_id', '=', self.survey_id.id), '|',
            ('partner_id', 'in', partners.ids), ('email', 'in', emails)
        ])
        partners_done = self.env['res.partner']
        emails_done = []
        if existing_answers:
            if self.existing_mode == 'resend':
                partners_done = existing_answers.mapped('partner_id')
                emails_done = existing_answers.mapped('email')

                # only add the last answer for each user of each type (partner_id & email)
                # to have only one mail sent per user
                for partner_done in partners_done:
                    answers |= next(
                        existing_answer
                        for existing_answer in existing_answers.sorted(
                            lambda answer: answer.create_date, reverse=True)
                        if existing_answer.partner_id == partner_done)

                for email_done in emails_done:
                    answers |= next(
                        existing_answer
                        for existing_answer in existing_answers.sorted(
                            lambda answer: answer.create_date, reverse=True)
                        if existing_answer.email == email_done)

        for new_partner in partners - partners_done:
            answers |= self.survey_id._create_answer(
                partner=new_partner,
                check_attempts=False,
                **self._get_answers_values())
        for new_email in [
                email for email in emails if email not in emails_done
        ]:
            answers |= self.survey_id._create_answer(
                email=new_email,
                check_attempts=False,
                **self._get_answers_values())

        return answers

    def _get_answers_values(self):
        return {
            'input_type': 'link',
            'deadline': self.deadline,
        }

    def _send_mail(self, answer):
        """ Create mail specific for recipient containing notably its access token """
        subject = self.env['mail.template']._render_template(
            self.subject, 'survey.user_input', answer.id, post_process=True)
        body = self.env['mail.template']._render_template(self.body,
                                                          'survey.user_input',
                                                          answer.id,
                                                          post_process=True)
        # post the message
        mail_values = {
            'email_from': self.email_from,
            'author_id': self.author_id.id,
            'model': None,
            'res_id': None,
            'subject': subject,
            'body_html': body,
            'attachment_ids': [(4, att.id) for att in self.attachment_ids],
            'auto_delete': True,
        }
        if answer.partner_id:
            mail_values['recipient_ids'] = [(4, answer.partner_id.id)]
        else:
            mail_values['email_to'] = answer.email

        # optional support of notif_layout in context
        notif_layout = self.env.context.get(
            'notif_layout', self.env.context.get('custom_layout'))
        if notif_layout:
            try:
                template = self.env.ref(notif_layout, raise_if_not_found=True)
            except ValueError:
                _logger.warning(
                    'QWeb template %s not found when sending survey mails. Sending without layouting.'
                    % (notif_layout))
            else:
                template_ctx = {
                    'message':
                    self.env['mail.message'].sudo().new(
                        dict(body=mail_values['body_html'],
                             record_name=self.survey_id.title)),
                    'model_description':
                    self.env['ir.model']._get('survey.survey').display_name,
                    'company':
                    self.env.company,
                }
                body = template.render(template_ctx,
                                       engine='ir.qweb',
                                       minimal_qcontext=True)
                mail_values['body_html'] = self.env[
                    'mail.thread']._replace_local_links(body)

        return self.env['mail.mail'].sudo().create(mail_values)

    def action_invite(self):
        """ Process the wizard content and proceed with sending the related
            email(s), rendering any template patterns on the fly if needed """
        self.ensure_one()
        Partner = self.env['res.partner']

        # compute partners and emails, try to find partners for given emails
        valid_partners = self.partner_ids
        valid_emails = []
        for email in emails_split.split(self.emails or ''):
            partner = False
            email_normalized = tools.email_normalize(email)
            if email_normalized:
                partner = Partner.search([('email_normalized', '=',
                                           email_normalized)])
            if partner:
                valid_partners |= partner
            else:
                email_formatted = tools.email_split_and_format(email)
                if email_formatted:
                    valid_emails.extend(email_formatted)

        if not valid_partners and not valid_emails:
            raise UserError(_("Please enter at least one valid recipient."))

        answers = self._prepare_answers(valid_partners, valid_emails)
        for answer in answers:
            self._send_mail(answer)

        return {'type': 'ir.actions.act_window_close'}
Пример #25
0
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="Date on which this document has been created")
    state = fields.Selection([('draft', 'Draft RFQ'), ('sent', 'RFQ Sent'),
                              ('to approve', 'To Approve'),
                              ('purchase', 'Purchase Order'), ('done', 'Done'),
                              ('cancel', 'Cancelled')],
                             'Order 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)
    delay_pass = fields.Float('Days to Receive', digits=(16, 2), readonly=True)
    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)

    def init(self):
        # self._table = sale_report
        tools.drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute(
            """CREATE or REPLACE VIEW %s as (
            %s
            FROM ( %s )
            %s
            )""" %
            (self._table, 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) 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) 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) 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 = """
            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())))
        """
        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
        """
        return group_by_str
class EagleeduStudent(models.Model):
    _name = 'eagleedu.student'
    _inherits = {'res.partner': 'partner_id'}
    _description = 'This the application for student'
    _order = 'id desc'
    _rec_name = 'name'

    @api.model
    def name_search(self, name, args=None, operator='ilike', limit=100):
        if name:
            recs = self.search([('name', operator, name)] + (args or []),
                               limit=limit)
            if not recs:
                recs = self.search([('adm_no', operator, name)] + (args or []),
                                   limit=limit)
            if not recs:
                recs = self.search([('application_no', operator, name)] +
                                   (args or []),
                                   limit=limit)
            return recs.name_get()
        return super(EagleeduStudent, self).name_search(name,
                                                        args=args,
                                                        operator=operator,
                                                        limit=limit)

    @api.model
    def create(self, vals):
        """Over riding the create method to assign sequence for the newly creating the record"""
        vals['adm_no'] = self.env['ir.sequence'].next_by_code(
            'eagleedu.student')
        res = super(EagleeduStudent, self).create(vals)
        return res

    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 required=True,
                                 ondelete="cascade")
    adm_no = fields.Char(string="Admission No.", readonly=True)
    st_image = fields.Binary(string='Image',
                             help="Provide the image of the Student")
    application_no = fields.Char(string='Application  No',
                                 required=True,
                                 copy=False,
                                 readonly=True,
                                 index=True,
                                 default=lambda self: _('New'))
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=lambda self: self.env.user.company_id)
    admission_class = fields.Many2one('eagleedu.class',
                                      string="Class Name",
                                      help="Enter Class Name")
    class_id = fields.Many2one('eagleedu.class.division',
                               string="Class Section")
    class_name = fields.Many2one('eagleedu.class', string="Class Name")
    section_id = fields.Many2one('eagleedu.class.section',
                                 related='class_id.section_id',
                                 string="Section",
                                 help="Enter Class Section Name")
    group_division = fields.Many2one('eagleedu.group_division',
                                     string="Group Name",
                                     help="Enter Class Section Name")
    academic_year = fields.Many2one('eagleedu.academic.year',
                                    string="Academic Year",
                                    help="Select Academic Year")
    academic_year_id = fields.Many2one('eagleedu.academic.year',
                                       related='class_id.academic_year_id',
                                       string="Academic Year",
                                       help="Select Academic Year")
    roll_no = fields.Integer(string="Roll No.", help="Enter Roll No.")

    st_name_b = fields.Char(string='Student Bangla Name')
    date_of_birth = fields.Date(string="Date Of birth")
    st_gender = fields.Selection([('male', 'Male'), ('female', 'Female'),
                                  ('other', 'Other')],
                                 string='Gender',
                                 required=False,
                                 track_visibility='onchange')
    st_blood_group = fields.Selection([('a+', 'A+'), ('a-', 'A-'),
                                       ('b+', 'B+'), ('o+', 'O+'),
                                       ('o-', 'O-'), ('ab-', 'AB-'),
                                       ('ab+', 'AB+')],
                                      string='Blood Group',
                                      track_visibility='onchange')
    st_passport_no = fields.Char(string="Passport No.",
                                 help="Proud to say my father is",
                                 required=False)
    application_no = fields.Char(string='Registration No',
                                 required=True,
                                 copy=False,
                                 readonly=True,
                                 index=True,
                                 default=lambda self: _('New'))
    registration_date = fields.Datetime(
        'Registration Date', default=lambda self: fields.datetime.now(
        ))  # , default=fields.Datetime.now, required=True

    st_father_name = fields.Char(string="Father's Name",
                                 help="Proud to say my father is",
                                 required=False)
    st_father_name_b = fields.Char(string="বাবার নাম",
                                   help="Proud to say my father is")
    st_father_occupation = fields.Char(string="Father's Occupation",
                                       help="father Occupation")
    st_father_email = fields.Char(string="Father's Email",
                                  help="father Occupation")
    father_mobile = fields.Char(string="Father's Mobile No",
                                help="Father's Mobile No")
    st_mother_name = fields.Char(string="Mother's Name",
                                 help="Proud to say my mother is",
                                 required=False)
    st_mother_name_b = fields.Char(string="মা এর নাম",
                                   help="Proud to say my mother is")
    st_mother_occupation = fields.Char(string="Mother Occupation",
                                       help="Proud to say my mother is")
    st_mother_email = fields.Char(string="Mother Email",
                                  help="Proud to say my mother is")
    mother_mobile = fields.Char(string="Mother's Mobile No",
                                help="mother's Mobile No")

    house_no = fields.Char(string='House No.', help="Enter the House No.")
    road_no = fields.Char(string='Area/Road No.',
                          help="Enter the Area or Road No.")
    post_office = fields.Char(string='Post Office',
                              help="Enter the Post Office Name")
    city = fields.Char(string='City', help="Enter the City name")
    bd_division_id = fields.Many2one('eagleedu.bddivision', string='Division')
    country_id = fields.Many2one('res.country',
                                 string='Country',
                                 ondelete='restrict',
                                 default=19)

    if_same_address = fields.Boolean(string="Permanent Address same as above",
                                     default=True)
    per_village = fields.Char(string='Village Name',
                              help="Enter the Village Name")
    per_po = fields.Char(string='Post Office Name',
                         help="Enter the Post office Name ")
    per_ps = fields.Char(string='Police Station',
                         help="Enter the Police Station Name")
    per_dist_id = fields.Many2one('eagleedu.bddistrict',
                                  string='District',
                                  help="Enter the City of District name")
    per_bd_division_id = fields.Many2one('eagleedu.bddivision',
                                         string='Division')
    per_country_id = fields.Many2one('res.country',
                                     string='Country',
                                     ondelete='restrict',
                                     default=19)

    guardian_name = fields.Char(string="Guardian's Name",
                                help="Proud to say my guardian is")
    # guardian_relation = fields.Many2one('eagleedu.guardian.relation', string="Guardian's Relation", help="Proud to say my guardian is")
    guardian_mobile = fields.Char(string="Guardian's Mobile")

    religious_id = fields.Many2one('eagleedu.religious',
                                   string="Religious",
                                   help="My Religion is ")
    student_id = fields.Char('Student Id')
    section = fields.Char('Section', help="for import only")
    email = fields.Char(string="Student Email",
                        help="Enter E-mail id for contact purpose")
    phone = fields.Char(string="Student Phone",
                        help="Enter Phone no. for contact purpose")
    mobile = fields.Char(string="Student Mobile",
                         help="Enter Mobile num for contact purpose")
    nationality = fields.Many2one('res.country',
                                  string='Nationality',
                                  ondelete='restrict',
                                  default=19,
                                  help="Select the Nationality")
    assigned = fields.Boolean(default=False)
    application_id = fields.Many2one('eagleedu.application',
                                     string="Application No")

    @api.onchange('class_id')
    def set_div_ay_sec(self):
        self.ay_code = self.name
Пример #27
0
class ProductTemplate(models.Model):
    _inherit = [
        "product.template", "website.seo.metadata",
        'website.published.multi.mixin', 'rating.mixin', 'image.mixin'
    ]
    _name = 'product.template'
    _mail_post_access = 'read'

    partner_id = fields.Many2one('res.partner',
                                 string='Partner',
                                 ondelete="cascade")
    adm_no = fields.Char(string="Admission No.", readonly=True)
    image_1920 = fields.Binary(string='Image',
                               help="Provide the image of the Human")
    application_no = fields.Char(string='Application  No',
                                 required=True,
                                 copy=False,
                                 readonly=True,
                                 index=True,
                                 default=lambda self: _('New'))
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=lambda self: self.env.user.company_id)

    date_of_birth = fields.Date(string="Date Of birth")
    st_gender = fields.Selection([('male', 'Male'), ('female', 'Female'),
                                  ('other', 'Other')],
                                 string='Gender',
                                 required=False,
                                 track_visibility='onchange')
    st_blood_group = fields.Selection([('a+', 'A+'), ('a-', 'A-'),
                                       ('b+', 'B+'), ('o+', 'O+'),
                                       ('o-', 'O-'), ('ab-', 'AB-'),
                                       ('ab+', 'AB+')],
                                      string='Blood Group',
                                      track_visibility='onchange')
    application_no = fields.Char(string='Registration No',
                                 required=True,
                                 copy=False,
                                 readonly=True,
                                 index=True,
                                 default=lambda self: _('New'))
    registration_date = fields.Datetime(
        'Registration Date', default=lambda self: fields.datetime.now(
        ))  # , default=fields.Datetime.now, required=True

    st_father_name = fields.Char(string="Father's Name",
                                 help="Proud to say my father is",
                                 required=False)
    st_mother_name = fields.Char(string="Mother's Name",
                                 help="Proud to say my mother is",
                                 required=False)

    house_no = fields.Char(string='House No.', help="Enter the House No.")
    road_no = fields.Char(string='Area/Road No.',
                          help="Enter the Area or Road No.")
    post_office = fields.Char(string='Post Office',
                              help="Enter the Post Office Name")
    city = fields.Char(string='City', help="Enter the City name")
    bd_division_id = fields.Many2one('eagleedu.bddivision', string='Division')
    country_id = fields.Many2one('res.country',
                                 string='Country',
                                 ondelete='restrict',
                                 default=19)

    if_same_address = fields.Boolean(string="Permanent Address same as above",
                                     default=True)
    per_village = fields.Char(string='Village Name',
                              help="Enter the Village Name")
    per_po = fields.Char(string='Post Office Name',
                         help="Enter the Post office Name ")
    per_ps = fields.Char(string='Police Station',
                         help="Enter the Police Station Name")
    per_dist_id = fields.Many2one('eagleedu.bddistrict',
                                  string='District',
                                  help="Enter the City of District name")
    per_bd_division_id = fields.Many2one('eagleedu.bddivision',
                                         string='Division/Province',
                                         help="Enter the Division name")
    per_country_id = fields.Many2one('res.country',
                                     string='Country',
                                     ondelete='restrict',
                                     default=19)

    religious_id = fields.Many2one('eagleedu.religious',
                                   string="Religious",
                                   help="My Religion is ")
    email = fields.Char(string="Email",
                        help="Enter E-mail id for contact purpose")
    mobile = fields.Char(string="Mobile",
                         help="Enter Mobile num for contact purpose")
    nationality = fields.Many2one('res.country',
                                  string='Nationality',
                                  ondelete='restrict',
                                  default=19,
                                  help="Select the Nationality")
    state = fields.Selection([('draft', 'Draft'), ('approve', 'Approve'),
                              ('done', 'Done')],
                             string='Status',
                             required=True,
                             default='draft',
                             track_visibility='onchange')

    description_sale = fields.Text(string="Description",
                                   help="Enter description")

    website_description = fields.Html('Description for the website',
                                      sanitize_attributes=False,
                                      translate=html_translate)
    alternative_product_ids = fields.Many2many(
        'product.template',
        'product_alternative_rel',
        'src_id',
        'dest_id',
        string='Alternative',
        help='Suggest alternatives for all (all strategy). '
        'Those show up on the page.')
    accessory_product_ids = fields.Many2many(
        'product.product',
        'product_accessory_rel',
        'src_id',
        'dest_id',
        string='Others ',
        help='Others show up when the reviews (all strategy).')
    website_size_x = fields.Integer('Size X', default=1)
    website_size_y = fields.Integer('Size Y', default=1)
    website_style_ids = fields.Many2many('product.style', string='Styles')
    website_sequence = fields.Integer(
        'Website Sequence',
        help="Determine the display in the Website",
        default=lambda self: self._default_website_sequence())
    public_categ_ids = fields.Many2many(
        'product.public.category',
        relation='product_public_category_product_template_rel',
        string='Website Category',
        help="This will be available in each mentioned category. > "
        "Customize and enable 'eCommerce categories' to view all categories.")

    product_template_image_ids = fields.One2many('product.image',
                                                 'product_tmpl_id',
                                                 string="Extra Media",
                                                 copy=True)

    def _has_no_variant_attributes(self):
        """Return whether this `product.template` has at least one no_variant
        attribute.

        :return: True if at least one no_variant attribute, False otherwise
        :rtype: bool
        """
        self.ensure_one()
        return any(a.create_variant == 'no_variant' for a in
                   self.valid_product_template_attribute_line_ids.attribute_id)

    def _has_is_custom_values(self):
        self.ensure_one()
        """Return whether this `product.template` has at least one is_custom
        attribute value.

        :return: True if at least one is_custom attribute value, False otherwise
        :rtype: bool
        """
        return any(v.is_custom
                   for v in self.valid_product_template_attribute_line_ids.
                   product_template_value_ids._only_active())

    def _get_possible_variants_sorted(self, parent_combination=None):
        self.ensure_one()

        def _sort_key_attribute_value(value):
            # if you change this order, keep it in sync with _order from `product.attribute`
            return (value.attribute_id.sequence, value.attribute_id.id)

        def _sort_key_variant(variant):
            keys = []
            for attribute in variant.product_template_attribute_value_ids.sorted(
                    _sort_key_attribute_value):
                # if you change this order, keep it in sync with _order from `product.attribute.value`
                keys.append(attribute.product_attribute_value_id.sequence)
                keys.append(attribute.id)
            return keys

        return self._get_possible_variants(parent_combination).sorted(
            _sort_key_variant)

    def _get_combination_info(self,
                              combination=False,
                              product_id=False,
                              add_qty=1,
                              pricelist=False,
                              parent_combination=False,
                              only_template=False):
        self.ensure_one()

        current_website = False

        if self.env.context.get('website_id'):
            current_website = self.env['website'].get_current_website()
            if not pricelist:
                pricelist = current_website.get_current_pricelist()

        combination_info = super(ProductTemplate, self)._get_combination_info(
            combination=combination,
            product_id=product_id,
            add_qty=add_qty,
            pricelist=pricelist,
            parent_combination=parent_combination,
            only_template=only_template)

        if self.env.context.get('website_id'):
            partner = self.env.user.partner_id
            company_id = current_website.company_id
            product = self.env['product.product'].browse(
                combination_info['product_id']) or self

            tax_display = self.env.user.has_group(
                'account.group_show_line_subtotals_tax_excluded'
            ) and 'total_excluded' or 'total_included'
            taxes = partner.property_account_position_id.map_tax(
                product.sudo().taxes_id.filtered(
                    lambda x: x.company_id == company_id), product, partner)

            # The list_price is always the price of one.
            quantity_1 = 1
            price = taxes.compute_all(combination_info['price'],
                                      pricelist.currency_id, quantity_1,
                                      product, partner)[tax_display]
            if pricelist.discount_policy == 'without_discount':
                list_price = taxes.compute_all(combination_info['list_price'],
                                               pricelist.currency_id,
                                               quantity_1, product,
                                               partner)[tax_display]
            else:
                list_price = price
            has_discounted_price = pricelist.currency_id.compare_amounts(
                list_price, price) == 1

            combination_info.update(
                price=price,
                list_price=list_price,
                has_discounted_price=has_discounted_price,
            )

        return combination_info

    def _create_first_product_variant(self, log_warning=False):
        return self._create_product_variant(
            self._get_first_possible_combination(), log_warning)

    def _get_current_company_fallback(self, **kwargs):
        """Override: if a website is set on the product or given, fallback to
        the company of the website. Otherwise use the one from parent method."""
        res = super(ProductTemplate,
                    self)._get_current_company_fallback(**kwargs)
        website = self.website_id or kwargs.get('website')
        return website and website.company_id or res

    def _default_website_sequence(self):
        ''' We want new product to be the last (highest seq).
        Every product should ideally have an unique sequence.
        Default sequence (10000) should only be used for DB first product.
        As we don't resequence the whole tree (as `sequence` does), this field
        might have negative value.
        '''
        self._cr.execute("SELECT MAX(website_sequence) FROM %s" % self._table)
        max_sequence = self._cr.fetchone()[0]
        if max_sequence is None:
            return 10000
        return max_sequence + 5

    def set_sequence_top(self):
        min_sequence = self.sudo().search([],
                                          order='website_sequence ASC',
                                          limit=1)
        self.website_sequence = min_sequence.website_sequence - 5

    def set_sequence_bottom(self):
        max_sequence = self.sudo().search([],
                                          order='website_sequence DESC',
                                          limit=1)
        self.website_sequence = max_sequence.website_sequence + 5

    def set_sequence_up(self):
        previous_product_tmpl = self.sudo().search(
            [
                ('website_sequence', '<', self.website_sequence),
                ('website_published', '=', self.website_published),
            ],
            order='website_sequence DESC',
            limit=1)
        if previous_product_tmpl:
            previous_product_tmpl.website_sequence, self.website_sequence = self.website_sequence, previous_product_tmpl.website_sequence
        else:
            self.set_sequence_top()

    def set_sequence_down(self):
        next_prodcut_tmpl = self.search([
            ('website_sequence', '>', self.website_sequence),
            ('website_published', '=', self.website_published),
        ],
                                        order='website_sequence ASC',
                                        limit=1)
        if next_prodcut_tmpl:
            next_prodcut_tmpl.website_sequence, self.website_sequence = self.website_sequence, next_prodcut_tmpl.website_sequence
        else:
            return self.set_sequence_bottom()

    def _default_website_meta(self):
        res = super(ProductTemplate, self)._default_website_meta()
        res['default_opengraph']['og:description'] = res['default_twitter'][
            'twitter:description'] = self.description_sale
        res['default_opengraph']['og:title'] = res['default_twitter'][
            'twitter:title'] = self.name
        res['default_opengraph']['og:image'] = res['default_twitter'][
            'twitter:image'] = self.env['website'].image_url(
                self, 'image_1024')
        res['default_meta_description'] = self.description_sale
        # res['default_meta_description'] = res['description_sale']['product.template'] = self.product.name
        #description_sale = self.env['product.template'].create(values)
        #student = self.env['product.template'].create(values)

        return res

    def _compute_website_url(self):
        super(ProductTemplate, self)._compute_website_url()
        for product in self:
            product.website_url = "/info/details/%s" % slug(product)

    # ---------------------------------------------------------
    # Rating Mixin API
    # ---------------------------------------------------------

    def _rating_domain(self):
        """ Only take the published rating into account to compute avg and count """
        domain = super(ProductTemplate, self)._rating_domain()
        return expression.AND([domain, [('website_published', '=', True)]])

    def _get_images(self):
        """Return a list of records implementing `image.mixin` to
        display on the carousel on the website for this template.

        This returns a list and not a recordset because the records might be
        from different models (template and image).

        It contains in this order: the main image of the template and the
        Template Extra Images.
        """
        self.ensure_one()
        return [self] + list(self.product_template_image_ids)
Пример #28
0
class ActivityReport(models.Model):
    """ CRM Lead Analysis """

    _name = "crm.activity.report"
    _auto = False
    _description = "CRM Activity Analysis"
    _rec_name = 'id'

    date = fields.Datetime('Date', readonly=True)
    author_id = fields.Many2one('res.partner', 'Created By', readonly=True)
    user_id = fields.Many2one('res.users', 'Salesperson', readonly=True)
    team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True)
    lead_id = fields.Many2one('crm.lead', "Lead", readonly=True)
    subject = fields.Char('Summary', readonly=True)
    subtype_id = fields.Many2one('mail.message.subtype',
                                 'Subtype',
                                 readonly=True)
    mail_activity_type_id = fields.Many2one('mail.activity.type',
                                            'Activity Type',
                                            readonly=True)
    country_id = fields.Many2one('res.country', 'Country', readonly=True)
    company_id = fields.Many2one('res.company', 'Company', readonly=True)
    stage_id = fields.Many2one('crm.stage', 'Stage', readonly=True)
    partner_id = fields.Many2one('res.partner',
                                 'Partner/Customer',
                                 readonly=True)
    lead_type = fields.Char(
        string='Type',
        selection=[('lead', 'Lead'), ('opportunity', 'Opportunity')],
        help="Type is used to separate Leads and Opportunities")
    active = fields.Boolean('Active', readonly=True)
    probability = fields.Float('Probability',
                               group_operator='avg',
                               readonly=True)

    def _select(self):
        return """
            SELECT
                m.id,
                m.subtype_id,
                m.mail_activity_type_id,
                m.author_id,
                m.date,
                m.subject,
                l.id as lead_id,
                l.user_id,
                l.team_id,
                l.country_id,
                l.company_id,
                l.stage_id,
                l.partner_id,
                l.type as lead_type,
                l.active,
                l.probability
        """

    def _from(self):
        return """
            FROM mail_message AS m
        """

    def _join(self):
        return """
            JOIN crm_lead AS l ON m.res_id = l.id
        """

    def _where(self):
        return """
            WHERE
                m.model = 'crm.lead' AND m.mail_activity_type_id IS NOT NULL
        """

    @api.model_cr
    def init(self):
        tools.drop_view_if_exists(self._cr, self._table)
        self._cr.execute("""
            CREATE OR REPLACE VIEW %s AS (
                %s
                %s
                %s
                %s
            )
        """ % (self._table, self._select(), self._from(), self._join(),
               self._where()))
Пример #29
0
class OpExam(models.Model):
    _name = "op.exam"
    _inherit = "mail.thread"
    _description = "Exam"

    session_id = fields.Many2one('op.exam.session', 'Exam Session',
                                 domain=[('state', 'not in',
                                          ['cancel', 'done'])])
    course_id = fields.Many2one(
        'op.course', related='session_id.course_id', store=True,
        readonly=True)
    batch_id = fields.Many2one(
        'op.batch', 'Batch', related='session_id.batch_id', store=True,
        readonly=True)
    subject_id = fields.Many2one('op.subject', 'Subject', required=True)
    exam_code = fields.Char('Exam Code', size=16, required=True)
    attendees_line = fields.One2many(
        'op.exam.attendees', 'exam_id', 'Attendees', readonly=True)
    start_time = fields.Datetime('Start Time', required=True)
    end_time = fields.Datetime('End Time', required=True)
    state = fields.Selection(
        [('draft', 'Draft'), ('schedule', 'Scheduled'), ('held', 'Held'),
         ('result_updated', 'Result Updated'),
         ('cancel', 'Cancelled'), ('done', 'Done')], 'State',
        readonly=True, default='draft', track_visibility='onchange')
    note = fields.Text('Note')
    responsible_id = fields.Many2many('op.faculty', string='Responsible')
    name = fields.Char('Exam', size=256, required=True)
    total_marks = fields.Integer('Total Marks', required=True)
    min_marks = fields.Integer('Passing Marks', required=True)

    _sql_constraints = [
        ('unique_exam_code',
         'unique(exam_code)', 'Code should be unique per exam!')]

    @api.constrains('total_marks', 'min_marks')
    def _check_marks(self):
        if self.total_marks <= 0.0 or self.min_marks <= 0.0:
            raise ValidationError(_('Enter proper marks!'))
        if self.min_marks > self.total_marks:
            raise ValidationError(_(
                "Passing Marks can't be greater than Total Marks"))

    @api.constrains('start_time', 'end_time')
    def _check_date_time(self):
        session_start = datetime.datetime.combine(
            fields.Date.from_string(self.session_id.start_date),
            datetime.time.min)
        session_end = datetime.datetime.combine(
            fields.Date.from_string(self.session_id.end_date),
            datetime.time.max)
        start_time = fields.Datetime.from_string(self.start_time)
        end_time = fields.Datetime.from_string(self.end_time)
        if start_time > end_time:
            raise ValidationError(_('End Time cannot be set \
            before Start Time.'))
        elif start_time < session_start or start_time > session_end or \
                end_time < session_start or end_time > session_end:
            raise ValidationError(
                _('Exam Time should in between Exam Session Dates.'))

    def act_result_updated(self):
        self.state = 'result_updated'

    def act_done(self):
        self.state = 'done'

    def act_draft(self):
        self.state = 'draft'

    def act_cancel(self):
        self.state = 'cancel'
Пример #30
0
class StockQuant(models.Model):
    _name = 'stock.quant'
    _description = 'Quants'
    _rec_name = 'product_id'

    def _domain_location_id(self):
        if not self._is_inventory_mode():
            return
        return [('usage', 'in', ['internal', 'transit'])]

    def _domain_lot_id(self):
        if not self._is_inventory_mode():
            return
        domain = [
            "'|'", "('company_id', '=', company_id)",
            "('company_id', '=', False)"
        ]
        if self.env.context.get('active_model') == 'product.product':
            domain.insert(
                0,
                "('product_id', '=', %s)" % self.env.context.get('active_id'))
        if self.env.context.get('active_model') == 'product.template':
            product_template = self.env['product.template'].browse(
                self.env.context.get('active_id'))
            if product_template.exists():
                domain.insert(
                    0, "('product_id', 'in', %s)" %
                    product_template.product_variant_ids.ids)
        return '[' + ', '.join(domain) + ']'

    def _domain_product_id(self):
        if not self._is_inventory_mode():
            return
        domain = [('type', '=', 'product')]
        if self.env.context.get('product_tmpl_id'):
            domain = expression.AND([
                domain,
                [('product_tmpl_id', '=', self.env.context['product_tmpl_id'])]
            ])
        return domain

    product_id = fields.Many2one('product.product',
                                 'Product',
                                 domain=lambda self: self._domain_product_id(),
                                 ondelete='restrict',
                                 readonly=True,
                                 required=True,
                                 index=True,
                                 check_company=True)
    # so user can filter on template in webclient
    product_tmpl_id = fields.Many2one('product.template',
                                      string='Product Template',
                                      related='product_id.product_tmpl_id',
                                      readonly=False)
    product_uom_id = fields.Many2one('uom.uom',
                                     'Unit of Measure',
                                     readonly=True,
                                     related='product_id.uom_id')
    company_id = fields.Many2one(related='location_id.company_id',
                                 string='Company',
                                 store=True,
                                 readonly=True)
    location_id = fields.Many2one(
        'stock.location',
        'Location',
        domain=lambda self: self._domain_location_id(),
        auto_join=True,
        ondelete='restrict',
        readonly=True,
        required=True,
        index=True,
        check_company=True)
    lot_id = fields.Many2one('stock.production.lot',
                             'Lot/Serial Number',
                             ondelete='restrict',
                             readonly=True,
                             check_company=True,
                             domain=lambda self: self._domain_lot_id())
    package_id = fields.Many2one('stock.quant.package',
                                 'Package',
                                 domain="[('location_id', '=', location_id)]",
                                 help='The package containing this quant',
                                 readonly=True,
                                 ondelete='restrict',
                                 check_company=True)
    owner_id = fields.Many2one('res.partner',
                               'Owner',
                               help='This is the owner of the quant',
                               readonly=True,
                               check_company=True)
    quantity = fields.Float(
        'Quantity',
        help=
        'Quantity of products in this quant, in the default unit of measure of the product',
        readonly=True)
    inventory_quantity = fields.Float('Inventoried Quantity',
                                      compute='_compute_inventory_quantity',
                                      inverse='_set_inventory_quantity',
                                      groups='stock.group_stock_manager')
    reserved_quantity = fields.Float(
        'Reserved Quantity',
        default=0.0,
        help=
        'Quantity of reserved products in this quant, in the default unit of measure of the product',
        readonly=True,
        required=True)
    in_date = fields.Datetime('Incoming Date', readonly=True)
    tracking = fields.Selection(related='product_id.tracking', readonly=True)

    @api.depends('quantity')
    def _compute_inventory_quantity(self):
        if not self._is_inventory_mode():
            self.inventory_quantity = 0
            return
        for quant in self:
            quant.inventory_quantity = quant.quantity

    def _set_inventory_quantity(self):
        """ Inverse method to create stock move when `inventory_quantity` is set
        (`inventory_quantity` is only accessible in inventory mode).
        """
        if not self._is_inventory_mode():
            return
        for quant in self:
            # Get the quantity to create a move for.
            rounding = quant.product_id.uom_id.rounding
            diff = float_round(quant.inventory_quantity - quant.quantity,
                               precision_rounding=rounding)
            diff_float_compared = float_compare(diff,
                                                0,
                                                precision_rounding=rounding)
            # Create and vaidate a move so that the quant matches its `inventory_quantity`.
            if diff_float_compared == 0:
                continue
            elif diff_float_compared > 0:
                move_vals = quant._get_inventory_move_values(
                    diff,
                    quant.product_id.with_context(
                        force_company=quant.company_id.id
                        or self.env.company.id).property_stock_inventory,
                    quant.location_id)
            else:
                move_vals = quant._get_inventory_move_values(
                    -diff,
                    quant.location_id,
                    quant.product_id.with_context(
                        force_company=quant.company_id.id
                        or self.env.company.id).property_stock_inventory,
                    out=True)
            move = quant.env['stock.move'].with_context(
                inventory_mode=False).create(move_vals)
            move._action_done()

    @api.model
    def create(self, vals):
        """ Override to handle the "inventory mode" and create a quant as
        superuser the conditions are met.
        """
        if self._is_inventory_mode() and 'inventory_quantity' in vals:
            allowed_fields = self._get_inventory_fields_create()
            if any([
                    field for field in vals.keys()
                    if field not in allowed_fields
            ]):
                raise UserError(
                    _("Quant's creation is restricted, you can't do this operation."
                      ))
            inventory_quantity = vals.pop('inventory_quantity')

            # Create an empty quant or write on a similar one.
            product = self.env['product.product'].browse(vals['product_id'])
            location = self.env['stock.location'].browse(vals['location_id'])
            lot_id = self.env['stock.production.lot'].browse(
                vals.get('lot_id'))
            package_id = self.env['stock.quant.package'].browse(
                vals.get('package_id'))
            owner_id = self.env['res.partner'].browse(vals.get('owner_id'))
            quant = self._gather(product,
                                 location,
                                 lot_id=lot_id,
                                 package_id=package_id,
                                 owner_id=owner_id,
                                 strict=True)
            if quant:
                quant = quant[0]
            else:
                quant = self.sudo().create(vals)
            # Set the `inventory_quantity` field to create the necessary move.
            quant.inventory_quantity = inventory_quantity
            return quant
        res = super(StockQuant, self).create(vals)
        if self._is_inventory_mode():
            res._check_company()
        return res

    @api.model
    def read_group(self,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   orderby=False,
                   lazy=True):
        """ Override to handle the "inventory mode" and set the `inventory_quantity`
        in view list grouped.
        """
        result = super(StockQuant, self).read_group(domain,
                                                    fields,
                                                    groupby,
                                                    offset=offset,
                                                    limit=limit,
                                                    orderby=orderby,
                                                    lazy=lazy)
        if self._is_inventory_mode():
            for record in result:
                record['inventory_quantity'] = record.get('quantity', 0)
        return result

    def write(self, vals):
        """ Override to handle the "inventory mode" and create the inventory move. """
        if self._is_inventory_mode() and 'inventory_quantity' in vals:
            if any(quant.location_id.usage == 'inventory' for quant in self):
                # Do nothing when user tries to modify manually a inventory loss
                return
            allowed_fields = self._get_inventory_fields_write()
            if any([
                    field for field in vals.keys()
                    if field not in allowed_fields
            ]):
                raise UserError(
                    _("Quant's edition is restricted, you can't do this operation."
                      ))
            self = self.sudo()
            return super(StockQuant, self).write(vals)
        return super(StockQuant, self).write(vals)

    def action_view_stock_moves(self):
        self.ensure_one()
        action = self.env.ref('stock.stock_move_line_action').read()[0]
        action['domain'] = [
            ('product_id', '=', self.product_id.id),
            '|',
            ('location_id', '=', self.location_id.id),
            ('location_dest_id', '=', self.location_id.id),
            ('lot_id', '=', self.lot_id.id),
            '|',
            ('package_id', '=', self.package_id.id),
            ('result_package_id', '=', self.package_id.id),
        ]
        return action

    @api.model
    def action_view_quants(self):
        self = self.with_context(search_default_internal_loc=1)
        if self.user_has_groups(
                'stock.group_production_lot,stock.group_stock_multi_locations'
        ):
            # fixme: erase the following condition when it'll be possible to create a new record
            # from a empty grouped editable list without go through the form view.
            if self.search_count([('company_id', '=', self.env.company.id),
                                  ('location_id.usage', 'in',
                                   ['internal', 'transit'])]):
                self = self.with_context(search_default_productgroup=1,
                                         search_default_locationgroup=1)
        if not self.user_has_groups('stock.group_stock_multi_locations'):
            company_user = self.env.company
            warehouse = self.env['stock.warehouse'].search(
                [('company_id', '=', company_user.id)], limit=1)
            if warehouse:
                self = self.with_context(
                    default_location_id=warehouse.lot_stock_id.id)

        # If user have rights to write on quant, we set quants in inventory mode.
        if self.user_has_groups('stock.group_stock_manager'):
            self = self.with_context(inventory_mode=True)
        return self._get_quants_action(extend=True)

    @api.constrains('product_id')
    def check_product_id(self):
        if any(elem.product_id.type != 'product' for elem in self):
            raise ValidationError(
                _('Quants cannot be created for consumables or services.'))

    @api.constrains('quantity')
    def check_quantity(self):
        for quant in self:
            if float_compare(
                    quant.quantity,
                    1,
                    precision_rounding=quant.product_uom_id.rounding
            ) > 0 and quant.lot_id and quant.product_id.tracking == 'serial':
                raise ValidationError(
                    _('A serial number should only be linked to a single product.'
                      ))

    @api.constrains('location_id')
    def check_location_id(self):
        for quant in self:
            if quant.location_id.usage == 'view':
                raise ValidationError(
                    _('You cannot take products from or deliver products to a location of type "view".'
                      ))

    def _compute_name(self):
        for quant in self:
            quant.name = '%s: %s%s' % (
                quant.lot_id.name or quant.product_id.code
                or '', quant.quantity, quant.product_id.uom_id.name)

    @api.model
    def _get_removal_strategy(self, product_id, location_id):
        if product_id.categ_id.removal_strategy_id:
            return product_id.categ_id.removal_strategy_id.method
        loc = location_id
        while loc:
            if loc.removal_strategy_id:
                return loc.removal_strategy_id.method
            loc = loc.location_id
        return 'fifo'

    @api.model
    def _get_removal_strategy_order(self, removal_strategy):
        if removal_strategy == 'fifo':
            return 'in_date ASC NULLS FIRST, id'
        elif removal_strategy == 'lifo':
            return 'in_date DESC NULLS LAST, id desc'
        raise UserError(
            _('Removal strategy %s not implemented.') % (removal_strategy, ))

    def _gather(self,
                product_id,
                location_id,
                lot_id=None,
                package_id=None,
                owner_id=None,
                strict=False):
        self.env['stock.quant'].flush(
            ['location_id', 'owner_id', 'package_id', 'lot_id', 'product_id'])
        self.env['product.product'].flush(['virtual_available'])
        removal_strategy = self._get_removal_strategy(product_id, location_id)
        removal_strategy_order = self._get_removal_strategy_order(
            removal_strategy)
        domain = [
            ('product_id', '=', product_id.id),
        ]
        if not strict:
            if lot_id:
                domain = expression.AND([[('lot_id', '=', lot_id.id)], domain])
            if package_id:
                domain = expression.AND([[('package_id', '=', package_id.id)],
                                         domain])
            if owner_id:
                domain = expression.AND([[('owner_id', '=', owner_id.id)],
                                         domain])
            domain = expression.AND([[('location_id', 'child_of',
                                       location_id.id)], domain])
        else:
            domain = expression.AND([[('lot_id', '=', lot_id and lot_id.id
                                       or False)], domain])
            domain = expression.AND([[
                ('package_id', '=', package_id and package_id.id or False)
            ], domain])
            domain = expression.AND([[
                ('owner_id', '=', owner_id and owner_id.id or False)
            ], domain])
            domain = expression.AND([[('location_id', '=', location_id.id)],
                                     domain])

        # Copy code of _search for special NULLS FIRST/LAST order
        self.check_access_rights('read')
        query = self._where_calc(domain)
        self._apply_ir_rules(query, 'read')
        from_clause, where_clause, where_clause_params = query.get_sql()
        where_str = where_clause and (" WHERE %s" % where_clause) or ''
        query_str = 'SELECT "%s".id FROM ' % self._table + from_clause + where_str + " ORDER BY " + removal_strategy_order
        self._cr.execute(query_str, where_clause_params)
        res = self._cr.fetchall()
        # No uniquify list necessary as auto_join is not applied anyways...
        return self.browse([x[0] for x in res])

    @api.model
    def _get_available_quantity(self,
                                product_id,
                                location_id,
                                lot_id=None,
                                package_id=None,
                                owner_id=None,
                                strict=False,
                                allow_negative=False):
        """ Return the available quantity, i.e. the sum of `quantity` minus the sum of
        `reserved_quantity`, for the set of quants sharing the combination of `product_id,
        location_id` if `strict` is set to False or sharing the *exact same characteristics*
        otherwise.
        This method is called in the following usecases:
            - when a stock move checks its availability
            - when a stock move actually assign
            - when editing a move line, to check if the new value is forced or not
            - when validating a move line with some forced values and have to potentially unlink an
              equivalent move line in another picking
        In the two first usecases, `strict` should be set to `False`, as we don't know what exact
        quants we'll reserve, and the characteristics are meaningless in this context.
        In the last ones, `strict` should be set to `True`, as we work on a specific set of
        characteristics.

        :return: available quantity as a float
        """
        self = self.sudo()
        quants = self._gather(product_id,
                              location_id,
                              lot_id=lot_id,
                              package_id=package_id,
                              owner_id=owner_id,
                              strict=strict)
        rounding = product_id.uom_id.rounding
        if product_id.tracking == 'none':
            available_quantity = sum(quants.mapped('quantity')) - sum(
                quants.mapped('reserved_quantity'))
            if allow_negative:
                return available_quantity
            else:
                return available_quantity if float_compare(
                    available_quantity, 0.0,
                    precision_rounding=rounding) >= 0.0 else 0.0
        else:
            availaible_quantities = {
                lot_id: 0.0
                for lot_id in list(set(quants.mapped('lot_id'))) +
                ['untracked']
            }
            for quant in quants:
                if not quant.lot_id:
                    availaible_quantities[
                        'untracked'] += quant.quantity - quant.reserved_quantity
                else:
                    availaible_quantities[
                        quant.
                        lot_id] += quant.quantity - quant.reserved_quantity
            if allow_negative:
                return sum(availaible_quantities.values())
            else:
                return sum([
                    available_quantity
                    for available_quantity in availaible_quantities.values()
                    if float_compare(
                        available_quantity, 0, precision_rounding=rounding) > 0
                ])

    @api.onchange('location_id', 'product_id', 'lot_id', 'package_id',
                  'owner_id')
    def _onchange_location_or_product_id(self):
        vals = {}

        # Once the new line is complete, fetch the new theoretical values.
        if self.product_id and self.location_id:
            # Sanity check if a lot has been set.
            if self.lot_id:
                if self.tracking == 'none' or self.product_id != self.lot_id.product_id:
                    vals['lot_id'] = None

            quants = self._gather(self.product_id,
                                  self.location_id,
                                  lot_id=self.lot_id,
                                  package_id=self.package_id,
                                  owner_id=self.owner_id,
                                  strict=True)
            reserved_quantity = sum(quants.mapped('reserved_quantity'))
            quantity = sum(quants.mapped('quantity'))

            vals['reserved_quantity'] = reserved_quantity
            # Update `quantity` only if the user manually updated `inventory_quantity`.
            if float_compare(
                    self.quantity,
                    self.inventory_quantity,
                    precision_rounding=self.product_uom_id.rounding) == 0:
                vals['quantity'] = quantity
            # Special case: directly set the quantity to one for serial numbers,
            # it'll trigger `inventory_quantity` compute.
            if self.lot_id and self.tracking == 'serial':
                vals['quantity'] = 1

        if vals:
            self.update(vals)

    @api.onchange('inventory_quantity')
    def _onchange_inventory_quantity(self):
        if self.location_id and self.location_id.usage == 'inventory':
            warning = {
                'title':
                _('You cannot modify inventory loss quantity'),
                'message':
                _('Editing quantities in an Inventory Adjustment location is forbidden,'
                  'those locations are used as counterpart when correcting the quantities.'
                  )
            }
            return {'warning': warning}

    @api.model
    def _update_available_quantity(self,
                                   product_id,
                                   location_id,
                                   quantity,
                                   lot_id=None,
                                   package_id=None,
                                   owner_id=None,
                                   in_date=None):
        """ Increase or decrease `reserved_quantity` of a set of quants for a given set of
        product_id/location_id/lot_id/package_id/owner_id.

        :param product_id:
        :param location_id:
        :param quantity:
        :param lot_id:
        :param package_id:
        :param owner_id:
        :param datetime in_date: Should only be passed when calls to this method are done in
                                 order to move a quant. When creating a tracked quant, the
                                 current datetime will be used.
        :return: tuple (available_quantity, in_date as a datetime)
        """
        self = self.sudo()
        quants = self._gather(product_id,
                              location_id,
                              lot_id=lot_id,
                              package_id=package_id,
                              owner_id=owner_id,
                              strict=True)

        incoming_dates = [d for d in quants.mapped('in_date') if d]
        incoming_dates = [
            fields.Datetime.from_string(incoming_date)
            for incoming_date in incoming_dates
        ]
        if in_date:
            incoming_dates += [in_date]
        # If multiple incoming dates are available for a given lot_id/package_id/owner_id, we
        # consider only the oldest one as being relevant.
        if incoming_dates:
            in_date = fields.Datetime.to_string(min(incoming_dates))
        else:
            in_date = fields.Datetime.now()

        for quant in quants:
            try:
                with self._cr.savepoint():
                    self._cr.execute(
                        "SELECT 1 FROM stock_quant WHERE id = %s FOR UPDATE NOWAIT",
                        [quant.id],
                        log_exceptions=False)
                    quant.write({
                        'quantity': quant.quantity + quantity,
                        'in_date': in_date,
                    })
                    break
            except OperationalError as e:
                if e.pgcode == '55P03':  # could not obtain the lock
                    continue
                else:
                    raise
        else:
            self.create({
                'product_id': product_id.id,
                'location_id': location_id.id,
                'quantity': quantity,
                'lot_id': lot_id and lot_id.id,
                'package_id': package_id and package_id.id,
                'owner_id': owner_id and owner_id.id,
                'in_date': in_date,
            })
        return self._get_available_quantity(
            product_id,
            location_id,
            lot_id=lot_id,
            package_id=package_id,
            owner_id=owner_id,
            strict=False,
            allow_negative=True), fields.Datetime.from_string(in_date)

    @api.model
    def _update_reserved_quantity(self,
                                  product_id,
                                  location_id,
                                  quantity,
                                  lot_id=None,
                                  package_id=None,
                                  owner_id=None,
                                  strict=False):
        """ Increase the reserved quantity, i.e. increase `reserved_quantity` for the set of quants
        sharing the combination of `product_id, location_id` if `strict` is set to False or sharing
        the *exact same characteristics* otherwise. Typically, this method is called when reserving
        a move or updating a reserved move line. When reserving a chained move, the strict flag
        should be enabled (to reserve exactly what was brought). When the move is MTS,it could take
        anything from the stock, so we disable the flag. When editing a move line, we naturally
        enable the flag, to reflect the reservation according to the edition.

        :return: a list of tuples (quant, quantity_reserved) showing on which quant the reservation
            was done and how much the system was able to reserve on it
        """
        self = self.sudo()
        rounding = product_id.uom_id.rounding
        quants = self._gather(product_id,
                              location_id,
                              lot_id=lot_id,
                              package_id=package_id,
                              owner_id=owner_id,
                              strict=strict)
        reserved_quants = []

        if float_compare(quantity, 0, precision_rounding=rounding) > 0:
            # if we want to reserve
            available_quantity = self._get_available_quantity(
                product_id,
                location_id,
                lot_id=lot_id,
                package_id=package_id,
                owner_id=owner_id,
                strict=strict)
            if float_compare(quantity,
                             available_quantity,
                             precision_rounding=rounding) > 0:
                raise UserError(
                    _('It is not possible to reserve more products of %s than you have in stock.'
                      ) % product_id.display_name)
        elif float_compare(quantity, 0, precision_rounding=rounding) < 0:
            # if we want to unreserve
            available_quantity = sum(quants.mapped('reserved_quantity'))
            if float_compare(abs(quantity),
                             available_quantity,
                             precision_rounding=rounding) > 0:
                raise UserError(
                    _('It is not possible to unreserve more products of %s than you have in stock.'
                      ) % product_id.display_name)
        else:
            return reserved_quants

        for quant in quants:
            if float_compare(quantity, 0, precision_rounding=rounding) > 0:
                max_quantity_on_quant = quant.quantity - quant.reserved_quantity
                if float_compare(max_quantity_on_quant,
                                 0,
                                 precision_rounding=rounding) <= 0:
                    continue
                max_quantity_on_quant = min(max_quantity_on_quant, quantity)
                quant.reserved_quantity += max_quantity_on_quant
                reserved_quants.append((quant, max_quantity_on_quant))
                quantity -= max_quantity_on_quant
                available_quantity -= max_quantity_on_quant
            else:
                max_quantity_on_quant = min(quant.reserved_quantity,
                                            abs(quantity))
                quant.reserved_quantity -= max_quantity_on_quant
                reserved_quants.append((quant, -max_quantity_on_quant))
                quantity += max_quantity_on_quant
                available_quantity += max_quantity_on_quant

            if float_is_zero(
                    quantity, precision_rounding=rounding) or float_is_zero(
                        available_quantity, precision_rounding=rounding):
                break
        return reserved_quants

    @api.model
    def _unlink_zero_quants(self):
        """ _update_available_quantity may leave quants with no
        quantity and no reserved_quantity. It used to directly unlink
        these zero quants but this proved to hurt the performance as
        this method is often called in batch and each unlink invalidate
        the cache. We defer the calls to unlink in this method.
        """
        precision_digits = max(
            6,
            self.sudo().env.ref('product.decimal_product_uom').digits * 2)
        # Use a select instead of ORM search for UoM robustness.
        query = """SELECT id FROM stock_quant WHERE round(quantity::numeric, %s) = 0 AND round(reserved_quantity::numeric, %s) = 0;"""
        params = (precision_digits, precision_digits)
        self.env.cr.execute(query, params)
        quant_ids = self.env['stock.quant'].browse(
            [quant['id'] for quant in self.env.cr.dictfetchall()])
        quant_ids.sudo().unlink()

    @api.model
    def _merge_quants(self):
        """ In a situation where one transaction is updating a quant via
        `_update_available_quantity` and another concurrent one calls this function with the same
        argument, we’ll create a new quant in order for these transactions to not rollback. This
        method will find and deduplicate these quants.
        """
        query = """WITH
                        dupes AS (
                            SELECT min(id) as to_update_quant_id,
                                (array_agg(id ORDER BY id))[2:array_length(array_agg(id), 1)] as to_delete_quant_ids,
                                SUM(reserved_quantity) as reserved_quantity,
                                SUM(quantity) as quantity
                            FROM stock_quant
                            GROUP BY product_id, company_id, location_id, lot_id, package_id, owner_id, in_date
                            HAVING count(id) > 1
                        ),
                        _up AS (
                            UPDATE stock_quant q
                                SET quantity = d.quantity,
                                    reserved_quantity = d.reserved_quantity
                            FROM dupes d
                            WHERE d.to_update_quant_id = q.id
                        )
                   DELETE FROM stock_quant WHERE id in (SELECT unnest(to_delete_quant_ids) from dupes)
        """
        try:
            with self.env.cr.savepoint():
                self.env.cr.execute(query)
        except Error as e:
            _logger.info('an error occured while merging quants: %s',
                         e.pgerror)

    @api.model
    def _quant_tasks(self):
        self._merge_quants()
        self._unlink_zero_quants()

    @api.model
    def _is_inventory_mode(self):
        """ Used to control whether a quant was written on or created during an
        "inventory session", meaning a mode where we need to create the stock.move
        record necessary to be consistent with the `inventory_quantity` field.
        """
        return self.env.context.get(
            'inventory_mode') is True and self.user_has_groups(
                'stock.group_stock_manager')

    @api.model
    def _get_inventory_fields_create(self):
        """ Returns a list of fields user can edit when he want to create a quant in `inventory_mode`.
        """
        return [
            'product_id', 'location_id', 'lot_id', 'package_id', 'owner_id',
            'inventory_quantity'
        ]

    @api.model
    def _get_inventory_fields_write(self):
        """ Returns a list of fields user can edit when he want to edit a quant in `inventory_mode`.
        """
        return ['inventory_quantity']

    def _get_inventory_move_values(self,
                                   qty,
                                   location_id,
                                   location_dest_id,
                                   out=False):
        """ Called when user manually set a new quantity (via `inventory_quantity`)
        just before creating the corresponding stock move.

        :param location_id: `stock.location`
        :param location_dest_id: `stock.location`
        :param out: boolean to set on True when the move go to inventory adjustment location.
        :return: dict with all values needed to create a new `stock.move` with its move line.
        """
        self.ensure_one()
        return {
            'name':
            _('Product Quantity Updated'),
            'product_id':
            self.product_id.id,
            'product_uom':
            self.product_uom_id.id,
            'product_uom_qty':
            qty,
            'company_id':
            self.company_id.id or self.env.user.company_id.id,
            'state':
            'confirmed',
            'location_id':
            location_id.id,
            'location_dest_id':
            location_dest_id.id,
            'move_line_ids': [(0, 0, {
                'product_id':
                self.product_id.id,
                'product_uom_id':
                self.product_uom_id.id,
                'qty_done':
                qty,
                'location_id':
                location_id.id,
                'location_dest_id':
                location_dest_id.id,
                'company_id':
                self.company_id.id or self.env.user.company_id.id,
                'lot_id':
                self.lot_id.id,
                'package_id':
                out and self.package_id.id or False,
                'result_package_id': (not out) and self.package_id.id or False,
                'owner_id':
                self.owner_id.id,
            })]
        }

    @api.model
    def _get_quants_action(self, domain=None, extend=False):
        """ Returns an action to open quant view.
        Depending of the context (user have right to be inventory mode or not),
        the list view will be editable or readonly.

        :param domain: List for the domain, empty by default.
        :param extend: If True, enables form, graph and pivot views. False by default.
        """
        self._quant_tasks()
        action = {
            'name':
            _('Update Quantity'),
            'view_type':
            'tree',
            'view_mode':
            'list',
            'res_model':
            'stock.quant',
            'type':
            'ir.actions.act_window',
            'context':
            dict(self.env.context),
            'domain':
            domain or [],
            'help':
            """
                <p class="o_view_nocontent_empty_folder">No Stock On Hand</p>
                <p>This analysis gives you an overview of the current stock
                level of your products.</p>
                """
        }

        if self._is_inventory_mode():
            action['view_id'] = self.env.ref(
                'stock.view_stock_quant_tree_editable').id
            # fixme: erase the following condition when it'll be possible to create a new record
            # from a empty grouped editable list without go through the form view.
            if not self.search_count(
                [('company_id', '=', self.env.company.id),
                 ('location_id.usage', 'in', ['internal', 'transit'])]):
                action['context'].update({
                    'search_default_productgroup': 0,
                    'search_default_locationgroup': 0
                })
        else:
            action['view_id'] = self.env.ref('stock.view_stock_quant_tree').id
            # Enables form view in readonly list
            action.update({
                'view_mode':
                'tree,form',
                'views': [
                    (action['view_id'], 'list'),
                    (self.env.ref('stock.view_stock_quant_form').id, 'form'),
                ],
            })
        if extend:
            action.update({
                'view_mode':
                'tree,form,pivot,graph',
                'views': [
                    (action['view_id'], 'list'),
                    (self.env.ref('stock.view_stock_quant_form').id, 'form'),
                    (self.env.ref('stock.view_stock_quant_pivot').id, 'pivot'),
                    (self.env.ref('stock.stock_quant_view_graph').id, 'graph'),
                ],
            })
        return action