Exemple #1
0
class HrPayslipRun(models.Model):
    _name = 'hr.payslip.run'
    _description = 'Payslip Batches'

    name = fields.Char(required=True, readonly=True, states={'draft': [('readonly', False)]})
    slip_ids = fields.One2many('hr.payslip', 'payslip_run_id', string='Payslips', readonly=True,
        states={'draft': [('readonly', False)]})
    state = fields.Selection([
        ('draft', 'Draft'),
        ('close', 'Close'),
    ], string='Status', index=True, readonly=True, copy=False, default='draft')
    date_start = fields.Date(string='Date From', required=True, readonly=True,
        states={'draft': [('readonly', False)]}, default=lambda self: fields.Date.to_string(date.today().replace(day=1)))
    date_end = fields.Date(string='Date To', required=True, readonly=True,
        states={'draft': [('readonly', False)]},
        default=lambda self: fields.Date.to_string((datetime.now() + relativedelta(months=+1, day=1, days=-1)).date()))
    credit_note = fields.Boolean(string='Credit Note', readonly=True,
        states={'draft': [('readonly', False)]},
        help="If its checked, indicates that all payslips generated from here are refund payslips.")

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

    @api.multi
    def close_payslip_run(self):
        return self.write({'state': 'close'})
    
    @api.multi
    def unlink(self):
        if any(self.filtered(lambda payslip_run: payslip_run.state not in ('draft'))):
            raise UserError(_('You cannot delete a payslip batch which is not draft!'))
        if any(self.mapped('slip_ids').filtered(lambda payslip: payslip.state not in ('draft','cancel'))):
            raise UserError(_('You cannot delete a payslip which is not draft or cancelled!'))
        return super(HrPayslipRun, self).unlink()
Exemple #2
0
class Product(models.Model):
    _inherit = 'product.template'

    membership = fields.Boolean(
        help='Check if the product is eligible for membership.')
    membership_date_from = fields.Date(
        string='Membership Start Date',
        help='Date from which membership becomes active.')
    membership_date_to = fields.Date(
        string='Membership End Date',
        help='Date until which membership remains active.')

    _sql_constraints = [
        ('membership_date_greater',
         'check(membership_date_to >= membership_date_from)',
         'Error ! Ending Date cannot be set before Beginning Date.')
    ]

    @api.model
    def fields_view_get(self,
                        view_id=None,
                        view_type='form',
                        toolbar=False,
                        submenu=False):
        if self._context.get('product') == 'membership_product':
            if view_type == 'form':
                view_id = self.env.ref(
                    'membership.membership_products_form').id
            else:
                view_id = self.env.ref(
                    'membership.membership_products_tree').id
        return super(Product, self).fields_view_get(view_id=view_id,
                                                    view_type=view_type,
                                                    toolbar=toolbar,
                                                    submenu=submenu)
class AccountFiscalYear(models.Model):
    _name = 'account.fiscal.year'
    _description = 'Fiscal Year'

    name = fields.Char(string='Name', required=True)
    date_from = fields.Date(string='Start Date',
                            required=True,
                            help='Start Date, included in the fiscal year.')
    date_to = fields.Date(string='End Date',
                          required=True,
                          help='Ending Date, included in the fiscal year.')
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 required=True,
                                 default=lambda self: self.env.user.company_id)

    @api.constrains('date_from', 'date_to', 'company_id')
    def _check_dates(self):
        '''
        Check interleaving between fiscal years.
        There are 3 cases to consider:

        s1   s2   e1   e2
        (    [----)----]

        s2   s1   e2   e1
        [----(----]    )

        s1   s2   e2   e1
        (    [----]    )
        '''
        for fy in self:
            # Starting date must be prior to the ending date
            date_from = fy.date_from
            date_to = fy.date_to
            if date_to < date_from:
                raise ValidationError(
                    _('The ending date must not be prior to the starting date.'
                      ))

            domain = [
                ('id', '!=', fy.id),
                ('company_id', '=', fy.company_id.id),
                '|',
                '|',
                '&',
                ('date_from', '<=', fy.date_from),
                ('date_to', '>=', fy.date_from),
                '&',
                ('date_from', '<=', fy.date_to),
                ('date_to', '>=', fy.date_to),
                '&',
                ('date_from', '<=', fy.date_from),
                ('date_to', '>=', fy.date_to),
            ]

            if self.search_count(domain) > 0:
                raise ValidationError(
                    _('You can not have an overlap between two fiscal years, please correct the start and/or end dates of your fiscal years.'
                      ))
Exemple #4
0
class FleetVehicleAssignationLog(models.Model):
    _name = "fleet.vehicle.assignation.log"
    _description = "Drivers history on a vehicle"
    _order = "date_start"

    vehicle_id = fields.Many2one('fleet.vehicle',
                                 string="Vehicle",
                                 required=True)
    driver_id = fields.Many2one('res.partner', string="Driver", required=True)
    date_start = fields.Date(string="Start Date")
    date_end = fields.Date(string="End Date")
Exemple #5
0
class ProductMargin(models.TransientModel):
    _name = 'product.margin'
    _description = 'Product Margin'

    from_date = fields.Date('From', default=time.strftime('%Y-01-01'))
    to_date = fields.Date('To', default=time.strftime('%Y-12-31'))
    invoice_state = fields.Selection([
        ('paid', 'Paid'),
        ('open_paid', 'Open and Paid'),
        ('draft_open_paid', 'Draft, Open and Paid'),
    ],
                                     'Invoice State',
                                     index=True,
                                     required=True,
                                     default="open_paid")

    @api.multi
    def action_open_window(self):
        self.ensure_one()
        context = dict(self.env.context or {})

        def ref(module, xml_id):
            proxy = self.env['ir.model.data']
            return proxy.get_object_reference(module, xml_id)

        model, search_view_id = ref('product', 'product_search_form_view')
        model, graph_view_id = ref('product_margin',
                                   'view_product_margin_graph')
        model, form_view_id = ref('product_margin', 'view_product_margin_form')
        model, tree_view_id = ref('product_margin', 'view_product_margin_tree')

        context.update(invoice_state=self.invoice_state)

        if self.from_date:
            context.update(date_from=self.from_date)

        if self.to_date:
            context.update(date_to=self.to_date)

        views = [(tree_view_id, 'tree'), (form_view_id, 'form'),
                 (graph_view_id, 'graph')]
        return {
            'name': _('Product Margins'),
            'context': context,
            'view_type': 'form',
            "view_mode": 'tree,form,graph',
            'res_model': 'product.product',
            'type': 'ir.actions.act_window',
            'views': views,
            'view_id': False,
            'search_view_id': search_view_id,
        }
class HrSalaryEmployeeBymonth(models.TransientModel):

    _name = 'hr.salary.employee.month'
    _description = 'Hr Salary Employee By Month Report'

    def _get_default_category(self):
        return self.env['hr.salary.rule.category'].search(
            [('code', '=', 'NET')], limit=1)

    def _get_default_start_date(self):
        year = fields.Date.from_string(fields.Date.today()).strftime('%Y')
        return '{}-01-01'.format(year)

    def _get_default_end_date(self):
        date = fields.Date.from_string(fields.Date.today())
        return date.strftime('%Y') + '-' + date.strftime(
            '%m') + '-' + date.strftime('%d')

    start_date = fields.Date(string='Start Date',
                             required=True,
                             default=_get_default_start_date)
    end_date = fields.Date(string='End Date',
                           required=True,
                           default=_get_default_end_date)
    employee_ids = fields.Many2many('hr.employee',
                                    'payroll_year_rel',
                                    'payroll_year_id',
                                    'employee_id',
                                    string='Employees',
                                    required=True)
    category_id = fields.Many2one('hr.salary.rule.category',
                                  string='Category',
                                  required=True,
                                  default=_get_default_category)

    @api.multi
    def print_report(self):
        """
         To get the date and print the report
         @return: return report
        """
        self.ensure_one()
        data = {'ids': self.env.context.get('active_ids', [])}
        res = self.read()
        res = res and res[0] or {}
        data.update({'form': res})
        return self.env.ref(
            'l10n_in_hr_payroll.action_report_hrsalarybymonth').report_action(
                self, data=data)
Exemple #7
0
class FleetVehicleOdometer(models.Model):
    _name = 'fleet.vehicle.odometer'
    _description = 'Odometer log for a vehicle'
    _order = 'date desc'

    name = fields.Char(compute='_compute_vehicle_log_name', store=True)
    date = fields.Date(default=fields.Date.context_today)
    value = fields.Float('Odometer Value', group_operator="max")
    vehicle_id = fields.Many2one('fleet.vehicle', 'Vehicle', required=True)
    unit = fields.Selection(related='vehicle_id.odometer_unit',
                            string="Unit",
                            readonly=True)
    driver_id = fields.Many2one(related="vehicle_id.driver_id",
                                string="Driver",
                                readonly=False)

    @api.depends('vehicle_id', 'date')
    def _compute_vehicle_log_name(self):
        for record in self:
            name = record.vehicle_id.name
            if not name:
                name = str(record.date)
            elif record.date:
                name += ' / ' + str(record.date)
            record.name = name

    @api.onchange('vehicle_id')
    def _onchange_vehicle(self):
        if self.vehicle_id:
            self.unit = self.vehicle_id.odometer_unit
Exemple #8
0
class HolidaysSummaryDept(models.TransientModel):

    _name = 'hr.holidays.summary.dept'
    _description = 'HR Leaves Summary Report By Department'

    date_from = fields.Date(string='From',
                            required=True,
                            default=lambda *a: time.strftime('%Y-%m-01'))
    depts = fields.Many2many('hr.department',
                             'summary_dept_rel',
                             'sum_id',
                             'dept_id',
                             string='Department(s)')
    holiday_type = fields.Selection([('Approved', 'Approved'),
                                     ('Confirmed', 'Confirmed'),
                                     ('both', 'Both Approved and Confirmed')],
                                    string='Leave Type',
                                    required=True,
                                    default='Approved')

    @api.multi
    def print_report(self):
        self.ensure_one()
        [data] = self.read()
        if not data.get('depts'):
            raise UserError(_('You have to select at least one department.'))
        departments = self.env['hr.department'].browse(data['depts'])
        datas = {'ids': [], 'model': 'hr.department', 'form': data}
        return self.env.ref(
            'hr_holidays.action_report_holidayssummary').with_context(
                from_transient_model=True).report_action(departments,
                                                         data=datas)
Exemple #9
0
class HolidaysSummaryEmployee(models.TransientModel):

    _name = 'hr.holidays.summary.employee'
    _description = 'HR Leaves Summary Report By Employee'

    date_from = fields.Date(string='From',
                            required=True,
                            default=lambda *a: time.strftime('%Y-%m-01'))
    emp = fields.Many2many('hr.employee',
                           'summary_emp_rel',
                           'sum_id',
                           'emp_id',
                           string='Employee(s)')
    holiday_type = fields.Selection([('Approved', 'Approved'),
                                     ('Confirmed', 'Confirmed'),
                                     ('both', 'Both Approved and Confirmed')],
                                    string='Select Leave Type',
                                    required=True,
                                    default='Approved')

    @api.multi
    def print_report(self):
        self.ensure_one()
        [data] = self.read()
        data['emp'] = self.env.context.get('active_ids', [])
        employees = self.env['hr.employee'].browse(data['emp'])
        datas = {'ids': [], 'model': 'hr.employee', 'form': data}
        return self.env.ref(
            'hr_holidays.action_report_holidayssummary').report_action(
                employees, data=datas)
Exemple #10
0
class ConverterTest(models.Model):
    _name = 'web_editor.converter.test'
    _description = 'Web Editor Converter Test'

    # disable translation export for those brilliant field labels and values
    _translate = False

    char = fields.Char()
    integer = fields.Integer()
    float = fields.Float()
    numeric = fields.Float(digits=(16, 2))
    many2one = fields.Many2one('web_editor.converter.test.sub')
    binary = fields.Binary()
    date = fields.Date()
    datetime = fields.Datetime()
    selection = fields.Selection([
        (1, "réponse A"),
        (2, "réponse B"),
        (3, "réponse C"),
        (4, "réponse <D>"),
    ])
    selection_str = fields.Selection([
        ('A', "Qu'il n'est pas arrivé à Toronto"),
        ('B', "Qu'il était supposé arriver à Toronto"),
        ('C', "Qu'est-ce qu'il fout ce maudit pancake, tabernacle ?"),
        ('D', "La réponse D"),
    ], string=u"Lorsqu'un pancake prend l'avion à destination de Toronto et "
              u"qu'il fait une escale technique à St Claude, on dit:")
    html = fields.Html()
    text = fields.Text()
Exemple #11
0
class CurrencyRate(models.Model):
    _name = "res.currency.rate"
    _description = "Currency Rate"
    _order = "name desc"

    name = fields.Date(string='Date',
                       required=True,
                       index=True,
                       default=lambda self: fields.Date.today())
    rate = fields.Float(
        digits=(12, 6),
        default=1.0,
        help='The rate of the currency to the currency of rate 1')
    currency_id = fields.Many2one('res.currency',
                                  string='Currency',
                                  readonly=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 default=lambda self: self.env.user.company_id)

    _sql_constraints = [
        ('unique_name_per_day', 'unique (name,currency_id,company_id)',
         'Only one currency rate per day allowed!'),
        ('currency_rate_check', 'CHECK (rate>0)',
         'The currency rate must be strictly positive.'),
    ]

    @api.model
    def _name_search(self,
                     name,
                     args=None,
                     operator='ilike',
                     limit=100,
                     name_get_uid=None):
        if operator in ['=', '!=']:
            try:
                date_format = '%Y-%m-%d'
                if self._context.get('lang'):
                    lang_id = self.env['res.lang']._search(
                        [('code', '=', self._context['lang'])],
                        access_rights_uid=name_get_uid)
                    if lang_id:
                        date_format = self.browse(lang_id).date_format
                name = time.strftime('%Y-%m-%d',
                                     time.strptime(name, date_format))
            except ValueError:
                try:
                    args.append(('rate', operator, float(name)))
                except ValueError:
                    return []
                name = ''
                operator = 'ilike'
        return super(CurrencyRate,
                     self)._name_search(name,
                                        args=args,
                                        operator=operator,
                                        limit=limit,
                                        name_get_uid=name_get_uid)
Exemple #12
0
class PayslipLinesContributionRegister(models.TransientModel):
    _name = 'payslip.lines.contribution.register'
    _description = 'Payslip Lines by Contribution Registers'

    date_from = fields.Date(string='Date From', required=True,
        default=datetime.now().strftime('%Y-%m-01'))
    date_to = fields.Date(string='Date To', required=True,
        default=str(datetime.now() + relativedelta.relativedelta(months=+1, day=1, days=-1))[:10])

    @api.multi
    def print_report(self):
        active_ids = self.env.context.get('active_ids', [])
        datas = {
             'ids': active_ids,
             'model': 'hr.contribution.register',
             'form': self.read()[0]
        }
        return self.env.ref('hr_payroll.action_contribution_register').report_action([], data=datas)
Exemple #13
0
class AccountAnalyticLine(models.Model):
    _name = 'account.analytic.line'
    _description = 'Analytic Line'
    _order = 'date desc, id desc'

    @api.model
    def _default_user(self):
        return self.env.context.get('user_id', self.env.user.id)

    name = fields.Char('Description', required=True)
    date = fields.Date('Date',
                       required=True,
                       index=True,
                       default=fields.Date.context_today)
    amount = fields.Monetary('Amount', required=True, default=0.0)
    unit_amount = fields.Float('Quantity', default=0.0)
    product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure')
    account_id = fields.Many2one('account.analytic.account',
                                 'Analytic Account',
                                 required=True,
                                 ondelete='restrict',
                                 index=True)
    partner_id = fields.Many2one('res.partner', string='Partner')
    user_id = fields.Many2one('res.users',
                              string='User',
                              default=_default_user)
    tag_ids = fields.Many2many('account.analytic.tag',
                               'account_analytic_line_tag_rel',
                               'line_id',
                               'tag_id',
                               string='Tags',
                               copy=True)
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 required=True,
                                 readonly=True,
                                 default=lambda self: self.env.user.company_id)
    currency_id = fields.Many2one(related="company_id.currency_id",
                                  string="Currency",
                                  readonly=True,
                                  store=True,
                                  compute_sudo=True)
    group_id = fields.Many2one('account.analytic.group',
                               related='account_id.group_id',
                               store=True,
                               readonly=True,
                               compute_sudo=True)

    @api.multi
    @api.constrains('company_id', 'account_id')
    def _check_company_id(self):
        for line in self:
            if line.account_id.company_id and line.company_id.id != line.account_id.company_id.id:
                raise ValidationError(
                    _('The selected account belongs to another company that the one you\'re trying to create an analytic item for'
                      ))
Exemple #14
0
class ComplexModel(models.Model):
    _name = name('complex')
    _description = 'Tests: Base Import Model Complex'

    f = fields.Float()
    m = fields.Monetary()
    c = fields.Char()
    currency_id = fields.Many2one('res.currency')
    d = fields.Date()
    dt = fields.Datetime()
class YearlySalaryDetail(models.TransientModel):
    _name = 'yearly.salary.detail'
    _description = 'Hr Salary Employee By Category Report'

    def _get_default_date_from(self):
        year = fields.Date.from_string(fields.Date.today()).strftime('%Y')
        return '{}-01-01'.format(year)

    def _get_default_date_to(self):
        date = fields.Date.from_string(fields.Date.today())
        return date.strftime('%Y') + '-' + date.strftime(
            '%m') + '-' + date.strftime('%d')

    employee_ids = fields.Many2many('hr.employee',
                                    'payroll_emp_rel',
                                    'payroll_id',
                                    'employee_id',
                                    string='Employees',
                                    required=True)
    date_from = fields.Date(string='Start Date',
                            required=True,
                            default=_get_default_date_from)
    date_to = fields.Date(string='End Date',
                          required=True,
                          default=_get_default_date_to)

    @api.multi
    def print_report(self):
        """
         To get the date and print the report
         @return: return report
        """
        self.ensure_one()
        data = {'ids': self.env.context.get('active_ids', [])}
        res = self.read()
        res = res and res[0] or {}
        data.update({'form': res})
        return self.env.ref(
            'l10n_in_hr_payroll.action_report_hryearlysalary').report_action(
                self, data=data)
class TimesheetAttendance(models.Model):
    _name = 'hr.timesheet.attendance.report'
    _auto = False
    _description = 'Timesheet Attendance Report'

    user_id = fields.Many2one('res.users')
    date = fields.Date()
    total_timesheet = fields.Float()
    total_attendance = fields.Float()
    total_difference = fields.Float()

    @api.model_cr
    def init(self):
        tools.drop_view_if_exists(self.env.cr, self._table)
        self._cr.execute("""CREATE OR REPLACE VIEW %s AS (
            SELECT
                max(id) AS id,
                t.user_id,
                t.date,
                coalesce(sum(t.attendance), 0) AS total_attendance,
                coalesce(sum(t.timesheet), 0) AS total_timesheet,
                coalesce(sum(t.attendance), 0) - coalesce(sum(t.timesheet), 0) as total_difference
            FROM (
                SELECT
                    -hr_attendance.id AS id,
                    resource_resource.user_id AS user_id,
                    hr_attendance.worked_hours AS attendance,
                    NULL AS timesheet,
                    hr_attendance.check_in::date AS date
                FROM hr_attendance
                LEFT JOIN hr_employee ON hr_employee.id = hr_attendance.employee_id
                LEFT JOIN resource_resource on resource_resource.id = hr_employee.resource_id
            UNION ALL
                SELECT
                    ts.id AS id,
                    ts.user_id AS user_id,
                    NULL AS attendance,
                    ts.unit_amount AS timesheet,
                    ts.date AS date
                FROM account_analytic_line AS ts
                WHERE ts.project_id IS NOT NULL
            ) AS t
            GROUP BY t.user_id, t.date
            ORDER BY t.date
        )
        """ % self._table)
Exemple #17
0
class ResPartner(models.Model):
    _inherit = "res.partner"

    partner_latitude = fields.Float(string='Geo Latitude', digits=(16, 5))
    partner_longitude = fields.Float(string='Geo Longitude', digits=(16, 5))
    date_localization = fields.Date(string='Geolocation Date')

    @classmethod
    def _geo_localize(cls,
                      apikey,
                      street='',
                      zip='',
                      city='',
                      state='',
                      country=''):
        search = geo_query_address(street=street,
                                   zip=zip,
                                   city=city,
                                   state=state,
                                   country=country)
        result = geo_find(search, apikey)
        if result is None:
            search = geo_query_address(city=city, state=state, country=country)
            result = geo_find(search, apikey)
        return result

    @api.multi
    def geo_localize(self):
        # We need country names in English below
        apikey = self.env['ir.config_parameter'].sudo().get_param(
            'google.api_key_geocode')
        for partner in self.with_context(lang='en_US'):
            result = partner._geo_localize(apikey, partner.street, partner.zip,
                                           partner.city, partner.state_id.name,
                                           partner.country_id.name)
            if result:
                partner.write({
                    'partner_latitude':
                    result[0],
                    'partner_longitude':
                    result[1],
                    'date_localization':
                    fields.Date.context_today(partner)
                })
        return True
Exemple #18
0
class StockInventory(models.Model):
    _inherit = "stock.inventory"

    accounting_date = fields.Date(
        'Accounting Date',
        help="Date at which the accounting entries will be created"
             " in case of automated inventory valuation."
             " If empty, the inventory date will be used.")

    @api.multi
    def post_inventory(self):
        res = True
        acc_inventories = self.filtered(lambda inventory: inventory.accounting_date)
        for inventory in acc_inventories:
            res = super(StockInventory, inventory.with_context(force_period_date=inventory.accounting_date)).post_inventory()
        other_inventories = self - acc_inventories
        if other_inventories:
            res = super(StockInventory, other_inventories).post_inventory()
        return res
Exemple #19
0
class Employee(models.Model):

    _inherit = "hr.employee"

    manager = fields.Boolean(string='Is a Manager')
    medic_exam = fields.Date(string='Medical Examination Date', groups="hr.group_hr_user")
    vehicle = fields.Char(string='Company Vehicle', groups="hr.group_hr_user")
    contract_ids = fields.One2many('hr.contract', 'employee_id', string='Employee Contracts')
    contract_id = fields.Many2one('hr.contract', compute='_compute_contract_id', string='Current Contract', help='Latest contract of the employee')
    contracts_count = fields.Integer(compute='_compute_contracts_count', string='Contract Count')

    def _compute_contract_id(self):
        """ get the lastest contract """
        Contract = self.env['hr.contract']
        for employee in self:
            employee.contract_id = Contract.search([('employee_id', '=', employee.id)], order='date_start desc', limit=1)

    def _compute_contracts_count(self):
        # read_group as sudo, since contract count is displayed on form view
        contract_data = self.env['hr.contract'].sudo().read_group([('employee_id', 'in', self.ids)], ['employee_id'], ['employee_id'])
        result = dict((data['employee_id'][0], data['employee_id_count']) for data in contract_data)
        for employee in self:
            employee.contracts_count = result.get(employee.id, 0)
Exemple #20
0
class AccountMoveReversal(models.TransientModel):
    """
    Account move reversal wizard, it cancel an account move by reversing it.
    """
    _name = 'account.move.reversal'
    _description = 'Account Move Reversal'

    date = fields.Date(string='Reversal date', default=fields.Date.context_today, required=True)
    journal_id = fields.Many2one('account.journal', string='Use Specific Journal', help='If empty, uses the journal of the journal entry to be reversed.')

    @api.multi
    def reverse_moves(self):
        ac_move_ids = self._context.get('active_ids', False)
        res = self.env['account.move'].browse(ac_move_ids).reverse_moves(self.date, self.journal_id or False)
        if res:
            return {
                'name': _('Reverse Moves'),
                'type': 'ir.actions.act_window',
                'view_type': 'form',
                'view_mode': 'tree,form',
                'res_model': 'account.move',
                'domain': [('id', 'in', res)],
            }
        return {'type': 'ir.actions.act_window_close'}
Exemple #21
0
class PricelistItem(models.Model):
    _name = "product.pricelist.item"
    _description = "Pricelist Item"
    _order = "applied_on, min_quantity desc, categ_id desc, id desc"
    # NOTE: if you change _order on this model, make sure it matches the SQL
    # query built in _compute_price_rule() above in this file to avoid
    # inconstencies and undeterministic issues.

    product_tmpl_id = fields.Many2one(
        'product.template',
        'Product Template',
        ondelete='cascade',
        help=
        "Specify a template if this rule only applies to one product template. Keep empty otherwise."
    )
    product_id = fields.Many2one(
        'product.product',
        'Product',
        ondelete='cascade',
        help=
        "Specify a product if this rule only applies to one product. Keep empty otherwise."
    )
    categ_id = fields.Many2one(
        'product.category',
        'Product Category',
        ondelete='cascade',
        help=
        "Specify a product category if this rule only applies to products belonging to this category or its children categories. Keep empty otherwise."
    )
    min_quantity = fields.Integer(
        'Min. Quantity',
        default=0,
        help="For the rule to apply, bought/sold quantity must be greater "
        "than or equal to the minimum quantity specified in this field.\n"
        "Expressed in the default unit of measure of the product.")
    applied_on = fields.Selection(
        [('3_global', 'Global'), ('2_product_category', ' Product Category'),
         ('1_product', 'Product'), ('0_product_variant', 'Product Variant')],
        "Apply On",
        default='3_global',
        required=True,
        help='Pricelist Item applicable on selected option')
    base = fields.Selection(
        [('list_price', 'Public Price'), ('standard_price', 'Cost'),
         ('pricelist', 'Other Pricelist')],
        "Based on",
        default='list_price',
        required=True,
        help='Base price for computation.\n'
        'Public Price: The base price will be the Sale/public Price.\n'
        'Cost Price : The base price will be the cost price.\n'
        'Other Pricelist : Computation of the base price based on another Pricelist.'
    )
    base_pricelist_id = fields.Many2one('product.pricelist', 'Other Pricelist')
    pricelist_id = fields.Many2one('product.pricelist',
                                   'Pricelist',
                                   index=True,
                                   ondelete='cascade')
    price_surcharge = fields.Float(
        'Price Surcharge',
        digits=dp.get_precision('Product Price'),
        help=
        'Specify the fixed amount to add or substract(if negative) to the amount calculated with the discount.'
    )
    price_discount = fields.Float('Price Discount', default=0, digits=(16, 2))
    price_round = fields.Float(
        'Price Rounding',
        digits=dp.get_precision('Product Price'),
        help="Sets the price so that it is a multiple of this value.\n"
        "Rounding is applied after the discount and before the surcharge.\n"
        "To have prices that end in 9.99, set rounding 10, surcharge -0.01")
    price_min_margin = fields.Float(
        'Min. Price Margin',
        digits=dp.get_precision('Product Price'),
        help='Specify the minimum amount of margin over the base price.')
    price_max_margin = fields.Float(
        'Max. Price Margin',
        digits=dp.get_precision('Product Price'),
        help='Specify the maximum amount of margin over the base price.')
    company_id = fields.Many2one('res.company',
                                 'Company',
                                 readonly=True,
                                 related='pricelist_id.company_id',
                                 store=True)
    currency_id = fields.Many2one('res.currency',
                                  'Currency',
                                  readonly=True,
                                  related='pricelist_id.currency_id',
                                  store=True)
    date_start = fields.Date(
        'Start Date', help="Starting date for the pricelist item validation")
    date_end = fields.Date(
        'End Date', help="Ending valid for the pricelist item validation")
    compute_price = fields.Selection([('fixed', 'Fix Price'),
                                      ('percentage', 'Percentage (discount)'),
                                      ('formula', 'Formula')],
                                     index=True,
                                     default='fixed')
    fixed_price = fields.Float('Fixed Price',
                               digits=dp.get_precision('Product Price'))
    percent_price = fields.Float('Percentage Price')
    # functional fields used for usability purposes
    name = fields.Char('Name',
                       compute='_get_pricelist_item_name_price',
                       help="Explicit rule name for this pricelist line.")
    price = fields.Char('Price',
                        compute='_get_pricelist_item_name_price',
                        help="Explicit rule name for this pricelist line.")

    @api.constrains('base_pricelist_id', 'pricelist_id', 'base')
    def _check_recursion(self):
        if any(item.base == 'pricelist' and item.pricelist_id
               and item.pricelist_id == item.base_pricelist_id
               for item in self):
            raise ValidationError(
                _('You cannot assign the Main Pricelist as Other Pricelist in PriceList Item'
                  ))
        return True

    @api.constrains('price_min_margin', 'price_max_margin')
    def _check_margin(self):
        if any(item.price_min_margin > item.price_max_margin for item in self):
            raise ValidationError(
                _('The minimum margin should be lower than the maximum margin.'
                  ))
        return True

    @api.one
    @api.depends('categ_id', 'product_tmpl_id', 'product_id', 'compute_price', 'fixed_price', \
        'pricelist_id', 'percent_price', 'price_discount', 'price_surcharge')
    def _get_pricelist_item_name_price(self):
        if self.categ_id:
            self.name = _("Category: %s") % (self.categ_id.name)
        elif self.product_tmpl_id:
            self.name = self.product_tmpl_id.name
        elif self.product_id:
            self.name = self.product_id.display_name.replace(
                '[%s]' % self.product_id.code, '')
        else:
            self.name = _("All Products")

        if self.compute_price == 'fixed':
            self.price = ("%s %s") % (float_repr(
                self.fixed_price,
                self.pricelist_id.currency_id.decimal_places,
            ), self.pricelist_id.currency_id.name)
        elif self.compute_price == 'percentage':
            self.price = _("%s %% discount") % (self.percent_price)
        else:
            self.price = _("%s %% discount and %s surcharge") % (
                self.price_discount, self.price_surcharge)

    @api.onchange('applied_on')
    def _onchange_applied_on(self):
        if self.applied_on != '0_product_variant':
            self.product_id = False
        if self.applied_on != '1_product':
            self.product_tmpl_id = False
        if self.applied_on != '2_product_category':
            self.categ_id = False

    @api.onchange('compute_price')
    def _onchange_compute_price(self):
        if self.compute_price != 'fixed':
            self.fixed_price = 0.0
        if self.compute_price != 'percentage':
            self.percent_price = 0.0
        if self.compute_price != 'formula':
            self.update({
                'price_discount': 0.0,
                'price_surcharge': 0.0,
                'price_round': 0.0,
                'price_min_margin': 0.0,
                'price_max_margin': 0.0,
            })

    @api.multi
    def write(self, values):
        res = super(PricelistItem, self).write(values)
        # When the pricelist changes we need the product.template price
        # to be invalided and recomputed.
        self.invalidate_cache()
        return res

    def _compute_price(self,
                       price,
                       price_uom,
                       product,
                       quantity=1.0,
                       partner=False):
        """Compute the unit price of a product in the context of a pricelist application.
           The unused parameters are there to make the full context available for overrides.
        """
        self.ensure_one()
        convert_to_price_uom = (
            lambda price: product.uom_id._compute_price(price, price_uom))
        if self.compute_price == 'fixed':
            price = convert_to_price_uom(self.fixed_price)
        elif self.compute_price == 'percentage':
            price = (price - (price * (self.percent_price / 100))) or 0.0
        else:
            # complete formula
            price_limit = price
            price = (price - (price * (self.price_discount / 100))) or 0.0
            if self.price_round:
                price = tools.float_round(price,
                                          precision_rounding=self.price_round)

            if self.price_surcharge:
                price_surcharge = convert_to_price_uom(self.price_surcharge)
                price += price_surcharge

            if self.price_min_margin:
                price_min_margin = convert_to_price_uom(self.price_min_margin)
                price = max(price, price_limit + price_min_margin)

            if self.price_max_margin:
                price_max_margin = convert_to_price_uom(self.price_max_margin)
                price = min(price, price_limit + price_max_margin)
        return price
Exemple #22
0
class MailActivityMixin(models.AbstractModel):
    """ Mail Activity Mixin is a mixin class to use if you want to add activities
    management on a model. It works like the mail.thread mixin. It defines
    an activity_ids one2many field toward activities using res_id and res_model_id.
    Various related / computed fields are also added to have a global status of
    activities on documents.

    Activities come with a new JS widget for the form view. It is integrated in the
    Chatter widget although it is a separate widget. It displays activities linked
    to the current record and allow to schedule, edit and mark done activities.
    Use widget="mail_activity" on activity_ids field in form view to use it.

    There is also a kanban widget defined. It defines a small widget to integrate
    in kanban vignettes. It allow to manage activities directly from the kanban
    view. Use widget="kanban_activity" on activitiy_ids field in kanban view to
    use it.

    Some context keys allow to control the mixin behavior. Use those in some
    specific cases like import

     * ``mail_activity_automation_skip``: skip activities automation; it means
       no automated activities will be generated, updated or unlinked, allowing
       to save computation and avoid generating unwanted activities;
    """
    _name = 'mail.activity.mixin'
    _description = 'Activity Mixin'

    activity_ids = fields.One2many(
        'mail.activity',
        'res_id',
        'Activities',
        auto_join=True,
        groups="base.group_user",
        domain=lambda self: [('res_model', '=', self._name)])
    activity_state = fields.Selection(
        [('overdue', 'Overdue'), ('today', 'Today'), ('planned', 'Planned')],
        string='Activity State',
        compute='_compute_activity_state',
        groups="base.group_user",
        help='Status based on activities\nOverdue: Due date is already passed\n'
        'Today: Activity date is today\nPlanned: Future activities.')
    activity_user_id = fields.Many2one('res.users',
                                       'Responsible User',
                                       related='activity_ids.user_id',
                                       readonly=False,
                                       search='_search_activity_user_id',
                                       groups="base.group_user")
    activity_type_id = fields.Many2one('mail.activity.type',
                                       'Next Activity Type',
                                       related='activity_ids.activity_type_id',
                                       readonly=False,
                                       search='_search_activity_type_id',
                                       groups="base.group_user")
    activity_date_deadline = fields.Date(
        'Next Activity Deadline',
        compute='_compute_activity_date_deadline',
        search='_search_activity_date_deadline',
        readonly=True,
        store=False,
        groups="base.group_user")
    activity_summary = fields.Char(
        'Next Activity Summary',
        related='activity_ids.summary',
        readonly=False,
        search='_search_activity_summary',
        groups="base.group_user",
    )

    @api.depends('activity_ids.state')
    def _compute_activity_state(self):
        for record in self:
            states = record.activity_ids.mapped('state')
            if 'overdue' in states:
                record.activity_state = 'overdue'
            elif 'today' in states:
                record.activity_state = 'today'
            elif 'planned' in states:
                record.activity_state = 'planned'

    @api.depends('activity_ids.date_deadline')
    def _compute_activity_date_deadline(self):
        for record in self:
            record.activity_date_deadline = record.activity_ids[:
                                                                1].date_deadline

    def _search_activity_date_deadline(self, operator, operand):
        if operator == '=' and not operand:
            return [('activity_ids', '=', False)]
        return [('activity_ids.date_deadline', operator, operand)]

    @api.model
    def _search_activity_user_id(self, operator, operand):
        return [('activity_ids.user_id', operator, operand)]

    @api.model
    def _search_activity_type_id(self, operator, operand):
        return [('activity_ids.activity_type_id', operator, operand)]

    @api.model
    def _search_activity_summary(self, operator, operand):
        return [('activity_ids.summary', operator, operand)]

    @api.multi
    def write(self, vals):
        # Delete activities of archived record.
        if 'active' in vals and vals['active'] is False:
            self.env['mail.activity'].sudo().search([
                ('res_model', '=', self._name), ('res_id', 'in', self.ids)
            ]).unlink()
        return super(MailActivityMixin, self).write(vals)

    @api.multi
    def unlink(self):
        """ Override unlink to delete records activities through (res_model, res_id). """
        record_ids = self.ids
        result = super(MailActivityMixin, self).unlink()
        self.env['mail.activity'].sudo().search([
            ('res_model', '=', self._name), ('res_id', 'in', record_ids)
        ]).unlink()
        return result

    @api.multi
    def toggle_active(self):
        """ Before archiving the record we should also remove its ongoing
        activities. Otherwise they stay in the systray and concerning archived
        records it makes no sense. """
        record_to_deactivate = self.filtered(lambda rec: rec.active)
        if record_to_deactivate:
            # use a sudo to bypass every access rights; all activities should be removed
            self.env['mail.activity'].sudo().search([
                ('res_model', '=', self._name),
                ('res_id', 'in', record_to_deactivate.ids)
            ]).unlink()
        return super(MailActivityMixin, self).toggle_active()

    def activity_send_mail(self, template_id):
        """ Automatically send an email based on the given mail.template, given
        its ID. """
        template = self.env['mail.template'].browse(template_id).exists()
        if not template:
            return False
        for record in self.with_context(mail_post_autofollow=True):
            record.message_post_with_template(template_id,
                                              composition_mode='comment')
        return True

    def activity_schedule(self,
                          act_type_xmlid='',
                          date_deadline=None,
                          summary='',
                          note='',
                          **act_values):
        """ Schedule an activity on each record of the current record set.
        This method allow to provide as parameter act_type_xmlid. This is an
        xml_id of activity type instead of directly giving an activity_type_id.
        It is useful to avoid having various "env.ref" in the code and allow
        to let the mixin handle access rights.

        :param date_deadline: the day the activity must be scheduled on
        the timezone of the user must be considered to set the correct deadline
        """
        if self.env.context.get('mail_activity_automation_skip'):
            return False

        if not date_deadline:
            date_deadline = fields.Date.context_today(self)
        if isinstance(date_deadline, datetime):
            _logger.warning("Scheduled deadline should be a date (got %s)",
                            date_deadline)
        if act_type_xmlid:
            activity_type = self.sudo().env.ref(act_type_xmlid)
        else:
            activity_type = self.env['mail.activity.type'].sudo().browse(
                act_values['activity_type_id'])

        model_id = self.env['ir.model']._get(self._name).id
        activities = self.env['mail.activity']
        for record in self:
            create_vals = {
                'activity_type_id': activity_type.id,
                'summary': summary or activity_type.summary,
                'automated': True,
                'note': note,
                'date_deadline': date_deadline,
                'res_model_id': model_id,
                'res_id': record.id,
            }
            create_vals.update(act_values)
            activities |= self.env['mail.activity'].create(create_vals)
        return activities

    def activity_schedule_with_view(self,
                                    act_type_xmlid='',
                                    date_deadline=None,
                                    summary='',
                                    views_or_xmlid='',
                                    render_context=None,
                                    **act_values):
        """ Helper method: Schedule an activity on each record of the current record set.
        This method allow to the same mecanism as `activity_schedule`, but provide
        2 additionnal parameters:
        :param views_or_xmlid: record of ir.ui.view or string representing the xmlid
            of the qweb template to render
        :type views_or_xmlid: string or recordset
        :param render_context: the values required to render the given qweb template
        :type render_context: dict
        """
        if self.env.context.get('mail_activity_automation_skip'):
            return False

        render_context = render_context or dict()
        if isinstance(views_or_xmlid, pycompat.string_types):
            views = self.env.ref(views_or_xmlid, raise_if_not_found=False)
        else:
            views = views_or_xmlid
        if not views:
            return
        activities = self.env['mail.activity']
        for record in self:
            render_context['object'] = record
            note = views.render(render_context,
                                engine='ir.qweb',
                                minimal_qcontext=True)
            activities |= record.activity_schedule(
                act_type_xmlid=act_type_xmlid,
                date_deadline=date_deadline,
                summary=summary,
                note=note,
                **act_values)
        return activities

    def activity_reschedule(self,
                            act_type_xmlids,
                            user_id=None,
                            date_deadline=None,
                            new_user_id=None):
        """ Reschedule some automated activities. Activities to reschedule are
        selected based on type xml ids and optionally by user. Purpose is to be
        able to

         * update the deadline to date_deadline;
         * update the responsible to new_user_id;
        """
        if self.env.context.get('mail_activity_automation_skip'):
            return False

        Data = self.env['ir.model.data'].sudo()
        activity_types_ids = [
            Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids
        ]
        domain = [
            '&', '&', '&', ('res_model', '=', self._name),
            ('res_id', 'in', self.ids), ('automated', '=', True),
            ('activity_type_id', 'in', activity_types_ids)
        ]
        if user_id:
            domain = ['&'] + domain + [('user_id', '=', user_id)]
        activities = self.env['mail.activity'].search(domain)
        if activities:
            write_vals = {}
            if date_deadline:
                write_vals['date_deadline'] = date_deadline
            if new_user_id:
                write_vals['user_id'] = new_user_id
            activities.write(write_vals)
        return activities

    def activity_feedback(self, act_type_xmlids, user_id=None, feedback=None):
        """ Set activities as done, limiting to some activity types and
        optionally to a given user. """
        if self.env.context.get('mail_activity_automation_skip'):
            return False

        Data = self.env['ir.model.data'].sudo()
        activity_types_ids = [
            Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids
        ]
        domain = [
            '&', '&', '&', ('res_model', '=', self._name),
            ('res_id', 'in', self.ids), ('automated', '=', True),
            ('activity_type_id', 'in', activity_types_ids)
        ]
        if user_id:
            domain = ['&'] + domain + [('user_id', '=', user_id)]
        activities = self.env['mail.activity'].search(domain)
        if activities:
            activities.action_feedback(feedback=feedback)
        return True

    def activity_unlink(self, act_type_xmlids, user_id=None):
        """ Unlink activities, limiting to some activity types and optionally
        to a given user. """
        if self.env.context.get('mail_activity_automation_skip'):
            return False

        Data = self.env['ir.model.data'].sudo()
        activity_types_ids = [
            Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids
        ]
        domain = [
            '&', '&', '&', ('res_model', '=', self._name),
            ('res_id', 'in', self.ids), ('automated', '=', True),
            ('activity_type_id', 'in', activity_types_ids)
        ]
        if user_id:
            domain = ['&'] + domain + [('user_id', '=', user_id)]
        self.env['mail.activity'].search(domain).unlink()
        return True
Exemple #23
0
class MailActivity(models.Model):
    """ An actual activity to perform. Activities are linked to
    documents using res_id and res_model_id fields. Activities have a deadline
    that can be used in kanban view to display a status. Once done activities
    are unlinked and a message is posted. This message has a new activity_type_id
    field that indicates the activity linked to the message. """
    _name = 'mail.activity'
    _description = 'Activity'
    _order = 'date_deadline ASC'
    _rec_name = 'summary'

    @api.model
    def default_get(self, fields):
        res = super(MailActivity, self).default_get(fields)
        if not fields or 'res_model_id' in fields and res.get('res_model'):
            res['res_model_id'] = self.env['ir.model']._get(
                res['res_model']).id
        return res

    # owner
    res_id = fields.Integer('Related Document ID', index=True, required=True)
    res_model_id = fields.Many2one('ir.model',
                                   'Document Model',
                                   index=True,
                                   ondelete='cascade',
                                   required=True)
    res_model = fields.Char('Related Document Model',
                            index=True,
                            related='res_model_id.model',
                            store=True,
                            readonly=True)
    res_name = fields.Char('Document Name',
                           compute='_compute_res_name',
                           store=True,
                           help="Display name of the related document.",
                           readonly=True)
    # activity
    activity_type_id = fields.Many2one(
        'mail.activity.type',
        'Activity',
        domain=
        "['|', ('res_model_id', '=', False), ('res_model_id', '=', res_model_id)]",
        ondelete='restrict')
    activity_category = fields.Selection(related='activity_type_id.category',
                                         readonly=True)
    activity_decoration = fields.Selection(
        related='activity_type_id.decoration_type', readonly=True)
    icon = fields.Char('Icon', related='activity_type_id.icon', readonly=True)
    summary = fields.Char('Summary')
    note = fields.Html('Note', sanitize_style=True)
    feedback = fields.Html('Feedback')
    date_deadline = fields.Date('Due Date',
                                index=True,
                                required=True,
                                default=fields.Date.context_today)
    automated = fields.Boolean(
        'Automated activity',
        readonly=True,
        help=
        'Indicates this activity has been created automatically and not by any user.'
    )
    # description
    user_id = fields.Many2one('res.users',
                              'Assigned to',
                              default=lambda self: self.env.user,
                              index=True,
                              required=True)
    create_user_id = fields.Many2one('res.users',
                                     'Creator',
                                     default=lambda self: self.env.user,
                                     index=True)
    state = fields.Selection([('overdue', 'Overdue'), ('today', 'Today'),
                              ('planned', 'Planned')],
                             'State',
                             compute='_compute_state')
    recommended_activity_type_id = fields.Many2one(
        'mail.activity.type', string="Recommended Activity Type")
    previous_activity_type_id = fields.Many2one(
        'mail.activity.type', string='Previous Activity Type', readonly=True)
    has_recommended_activities = fields.Boolean(
        'Next activities available',
        compute='_compute_has_recommended_activities',
        help='Technical field for UX purpose')
    mail_template_ids = fields.Many2many(
        related='activity_type_id.mail_template_ids', readonly=False)
    force_next = fields.Boolean(related='activity_type_id.force_next',
                                readonly=False)

    @api.multi
    @api.onchange('previous_activity_type_id')
    def _compute_has_recommended_activities(self):
        for record in self:
            record.has_recommended_activities = bool(
                record.previous_activity_type_id.next_type_ids)

    @api.multi
    @api.onchange('previous_activity_type_id')
    def _onchange_previous_activity_type_id(self):
        for record in self:
            if record.previous_activity_type_id.default_next_type_id:
                record.activity_type_id = record.previous_activity_type_id.default_next_type_id

    @api.depends('res_model', 'res_id')
    def _compute_res_name(self):
        for activity in self:
            activity.res_name = self.env[activity.res_model].browse(
                activity.res_id).name_get()[0][1]

    @api.depends('date_deadline')
    def _compute_state(self):
        for record in self.filtered(lambda activity: activity.date_deadline):
            tz = record.user_id.sudo().tz
            date_deadline = record.date_deadline
            record.state = self._compute_state_from_date(date_deadline, tz)

    @api.model
    def _compute_state_from_date(self, date_deadline, tz=False):
        date_deadline = fields.Date.from_string(date_deadline)
        today_default = date.today()
        today = today_default
        if tz:
            today_utc = pytz.UTC.localize(datetime.utcnow())
            today_tz = today_utc.astimezone(pytz.timezone(tz))
            today = date(year=today_tz.year,
                         month=today_tz.month,
                         day=today_tz.day)
        diff = (date_deadline - today)
        if diff.days == 0:
            return 'today'
        elif diff.days < 0:
            return 'overdue'
        else:
            return 'planned'

    @api.onchange('activity_type_id')
    def _onchange_activity_type_id(self):
        if self.activity_type_id:
            self.summary = self.activity_type_id.summary
            # Date.context_today is correct because date_deadline is a Date and is meant to be
            # expressed in user TZ
            base = fields.Date.context_today(self)
            if self.activity_type_id.delay_from == 'previous_activity' and 'activity_previous_deadline' in self.env.context:
                base = fields.Date.from_string(
                    self.env.context.get('activity_previous_deadline'))
            self.date_deadline = base + relativedelta(
                **{
                    self.activity_type_id.delay_unit:
                    self.activity_type_id.delay_count
                })

    @api.onchange('recommended_activity_type_id')
    def _onchange_recommended_activity_type_id(self):
        if self.recommended_activity_type_id:
            self.activity_type_id = self.recommended_activity_type_id

    @api.multi
    def _check_access(self, operation):
        """ Rule to access activities

         * create: check write rights on related document;
         * write: rule OR write rights on document;
         * unlink: rule OR write rights on document;
        """
        self.check_access_rights(
            operation, raise_exception=True)  # will raise an AccessError

        if operation in ('write', 'unlink'):
            try:
                self.check_access_rule(operation)
            except exceptions.AccessError:
                pass
            else:
                return
        doc_operation = 'read' if operation == 'read' else 'write'
        activity_to_documents = dict()
        for activity in self.sudo():
            activity_to_documents.setdefault(activity.res_model,
                                             list()).append(activity.res_id)
        for model, res_ids in activity_to_documents.items():
            self.env[model].check_access_rights(doc_operation,
                                                raise_exception=True)
            try:
                self.env[model].browse(res_ids).check_access_rule(
                    doc_operation)
            except exceptions.AccessError:
                raise exceptions.AccessError(
                    _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)'
                      ) % (self._description, operation) +
                    ' - ({} {}, {} {})'.format(_('Records:'), res_ids[:6],
                                               _('User:'******'read')
            except exceptions.AccessError:
                raise exceptions.UserError(
                    _('Assigned user %s has no access to the document and is not able to handle this activity.'
                      ) % activity.user_id.display_name)
            else:
                try:
                    target_user = activity.user_id
                    target_record = self.env[activity.res_model].browse(
                        activity.res_id)
                    if hasattr(target_record, 'company_id') and (
                            target_record.company_id != target_user.company_id
                            and (len(target_user.sudo().company_ids) > 1)):
                        return  # in that case we skip the check, assuming it would fail because of the company
                    model.browse(activity.res_id).check_access_rule('read')
                except exceptions.AccessError:
                    raise exceptions.UserError(
                        _('Assigned user %s has no access to the document and is not able to handle this activity.'
                          ) % activity.user_id.display_name)

    @api.model
    def create(self, values):
        # already compute default values to be sure those are computed using the current user
        values_w_defaults = self.default_get(self._fields.keys())
        values_w_defaults.update(values)

        # continue as sudo because activities are somewhat protected
        activity = super(MailActivity, self.sudo()).create(values_w_defaults)
        activity_user = activity.sudo(self.env.user)
        activity_user._check_access('create')
        need_sudo = False
        try:  # in multicompany, reading the partner might break
            partner_id = activity_user.user_id.partner_id.id
        except exceptions.AccessError:
            need_sudo = True
            partner_id = activity_user.user_id.sudo().partner_id.id

        # send a notification to assigned user; in case of manually done activity also check
        # target has rights on document otherwise we prevent its creation. Automated activities
        # are checked since they are integrated into business flows that should not crash.
        if activity_user.user_id != self.env.user:
            if not activity_user.automated:
                activity_user._check_access_assignation()
            if not self.env.context.get('mail_activity_quick_update', False):
                if need_sudo:
                    activity_user.sudo().action_notify()
                else:
                    activity_user.action_notify()

        self.env[activity_user.res_model].browse(
            activity_user.res_id).message_subscribe(partner_ids=[partner_id])
        if activity.date_deadline <= fields.Date.today():
            self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner',
                                         activity.user_id.partner_id.id), {
                                             'type': 'activity_updated',
                                             'activity_created': True
                                         })
        return activity_user

    @api.multi
    def write(self, values):
        self._check_access('write')
        if values.get('user_id'):
            pre_responsibles = self.mapped('user_id.partner_id')
        res = super(MailActivity, self.sudo()).write(values)

        if values.get('user_id'):
            if values['user_id'] != self.env.uid:
                to_check = self.filtered(lambda act: not act.automated)
                to_check._check_access_assignation()
                if not self.env.context.get('mail_activity_quick_update',
                                            False):
                    self.action_notify()
            for activity in self:
                self.env[activity.res_model].browse(
                    activity.res_id).message_subscribe(
                        partner_ids=[activity.user_id.partner_id.id])
                if activity.date_deadline <= fields.Date.today():
                    self.env['bus.bus'].sendone(
                        (self._cr.dbname, 'res.partner',
                         activity.user_id.partner_id.id), {
                             'type': 'activity_updated',
                             'activity_created': True
                         })
            for activity in self:
                if activity.date_deadline <= fields.Date.today():
                    for partner in pre_responsibles:
                        self.env['bus.bus'].sendone(
                            (self._cr.dbname, 'res.partner', partner.id), {
                                'type': 'activity_updated',
                                'activity_deleted': True
                            })
        return res

    @api.multi
    def unlink(self):
        self._check_access('unlink')
        for activity in self:
            if activity.date_deadline <= fields.Date.today():
                self.env['bus.bus'].sendone(
                    (self._cr.dbname, 'res.partner',
                     activity.user_id.sudo().partner_id.id), {
                         'type': 'activity_updated',
                         'activity_deleted': True
                     })
        return super(MailActivity, self.sudo()).unlink()

    @api.multi
    def name_get(self):
        res = []
        for record in self:
            name = record.summary or record.activity_type_id.display_name
            res.append((record.id, name))
        return res

    @api.multi
    def action_notify(self):
        body_template = self.env.ref('mail.message_activity_assigned')
        for activity in self:
            model_description = self.env['ir.model']._get(
                activity.res_model).display_name
            body = body_template.render(dict(
                activity=activity, model_description=model_description),
                                        engine='ir.qweb',
                                        minimal_qcontext=True)
            self.env['mail.thread'].message_notify(
                partner_ids=activity.user_id.partner_id.ids,
                body=body,
                subject=_('%s: %s assigned to you') %
                (activity.res_name, activity.summary
                 or activity.activity_type_id.name),
                record_name=activity.res_name,
                model_description=model_description,
                notif_layout='mail.mail_notification_light')

    @api.multi
    def action_done(self):
        """ Wrapper without feedback because web button add context as
        parameter, therefore setting context to feedback """
        return self.action_feedback()

    def action_feedback(self, feedback=False):
        message = self.env['mail.message']
        if feedback:
            self.write(dict(feedback=feedback))

        # Search for all attachments linked to the activities we are about to unlink. This way, we
        # can link them to the message posted and prevent their deletion.
        attachments = self.env['ir.attachment'].search_read([
            ('res_model', '=', self._name),
            ('res_id', 'in', self.ids),
        ], ['id', 'res_id'])

        activity_attachments = defaultdict(list)
        for attachment in attachments:
            activity_id = attachment['res_id']
            activity_attachments[activity_id].append(attachment['id'])

        for activity in self:
            record = self.env[activity.res_model].browse(activity.res_id)
            record.message_post_with_view(
                'mail.message_activity_done',
                values={'activity': activity},
                subtype_id=self.env['ir.model.data'].xmlid_to_res_id(
                    'mail.mt_activities'),
                mail_activity_type_id=activity.activity_type_id.id,
            )

            # Moving the attachments in the message
            # TODO: Fix void res_id on attachment when you create an activity with an image
            # directly, see route /web_editor/attachment/add
            activity_message = record.message_ids[0]
            message_attachments = self.env['ir.attachment'].browse(
                activity_attachments[activity.id])
            if message_attachments:
                message_attachments.write({
                    'res_id': activity_message.id,
                    'res_model': activity_message._name,
                })
                activity_message.attachment_ids = message_attachments
            message |= activity_message

        self.unlink()
        return message.ids and message.ids[0] or False

    def action_done_schedule_next(self):
        """ Wrapper without feedback because web button add context as
        parameter, therefore setting context to feedback """
        return self.action_feedback_schedule_next()

    @api.multi
    def action_feedback_schedule_next(self, feedback=False):
        ctx = dict(
            clean_context(self.env.context),
            default_previous_activity_type_id=self.activity_type_id.id,
            activity_previous_deadline=self.date_deadline,
            default_res_id=self.res_id,
            default_res_model=self.res_model,
        )
        force_next = self.force_next
        self.action_feedback(
            feedback)  # will unlink activity, dont access self after that
        if force_next:
            Activity = self.env['mail.activity'].with_context(ctx)
            res = Activity.new(Activity.default_get(Activity.fields_get()))
            res._onchange_previous_activity_type_id()
            res._onchange_activity_type_id()
            Activity.create(res._convert_to_write(res._cache))
            return False
        else:
            return {
                'name': _('Schedule an Activity'),
                'context': ctx,
                'view_type': 'form',
                'view_mode': 'form',
                'res_model': 'mail.activity',
                'views': [(False, 'form')],
                'type': 'ir.actions.act_window',
                'target': 'new',
            }

    @api.multi
    def action_close_dialog(self):
        return {'type': 'ir.actions.act_window_close'}

    @api.multi
    def activity_format(self):
        activities = self.read()
        mail_template_ids = set([
            template_id for activity in activities
            for template_id in activity["mail_template_ids"]
        ])
        mail_template_info = self.env["mail.template"].browse(
            mail_template_ids).read(['id', 'name'])
        mail_template_dict = dict([(mail_template['id'], mail_template)
                                   for mail_template in mail_template_info])
        for activity in activities:
            activity['mail_template_ids'] = [
                mail_template_dict[mail_template_id]
                for mail_template_id in activity['mail_template_ids']
            ]
        return activities

    @api.model
    def get_activity_data(self, res_model, domain):
        activity_domain = [('res_model', '=', res_model)]
        if domain:
            res = self.env[res_model].search(domain)
            activity_domain.append(('res_id', 'in', res.ids))
        grouped_activities = self.env['mail.activity'].read_group(
            activity_domain, [
                'res_id', 'activity_type_id', 'ids:array_agg(id)',
                'date_deadline:min(date_deadline)'
            ], ['res_id', 'activity_type_id'],
            lazy=False)
        # filter out unreadable records
        if not domain:
            res_ids = tuple(a['res_id'] for a in grouped_activities)
            res = self.env[res_model].search([('id', 'in', res_ids)])
            grouped_activities = [
                a for a in grouped_activities if a['res_id'] in res.ids
            ]
        activity_type_ids = self.env['mail.activity.type']
        res_id_to_deadline = {}
        activity_data = defaultdict(dict)
        for group in grouped_activities:
            res_id = group['res_id']
            activity_type_id = (group.get('activity_type_id')
                                or (False, False))[0]
            activity_type_ids |= self.env['mail.activity.type'].browse(
                activity_type_id
            )  # we will get the name when reading mail_template_ids
            res_id_to_deadline[res_id] = group['date_deadline'] if (
                res_id not in res_id_to_deadline or group['date_deadline'] <
                res_id_to_deadline[res_id]) else res_id_to_deadline[res_id]
            state = self._compute_state_from_date(group['date_deadline'],
                                                  self.user_id.sudo().tz)
            activity_data[res_id][activity_type_id] = {
                'count': group['__count'],
                'ids': group['ids'],
                'state': state,
                'o_closest_deadline': group['date_deadline'],
            }
        res_ids_sorted = sorted(res_id_to_deadline,
                                key=lambda item: res_id_to_deadline[item])
        res_id_to_name = dict(
            self.env[res_model].browse(res_ids_sorted).name_get())
        activity_type_infos = []
        for elem in sorted(activity_type_ids, key=lambda item: item.sequence):
            mail_template_info = []
            for mail_template_id in elem.mail_template_ids:
                mail_template_info.append({
                    "id": mail_template_id.id,
                    "name": mail_template_id.name
                })
            activity_type_infos.append(
                [elem.id, elem.name, mail_template_info])

        return {
            'activity_types': activity_type_infos,
            'res_ids': [(rid, res_id_to_name[rid]) for rid in res_ids_sorted],
            'grouped_activities': activity_data,
            'model': res_model,
        }
class PurchaseRequisition(models.Model):
    _name = "purchase.requisition"
    _description = "Purchase Requisition"
    _inherit = ['mail.thread']
    _order = "id desc"

    def _get_picking_in(self):
        pick_in = self.env.ref('stock.picking_type_in',
                               raise_if_not_found=False)
        company = self.env['res.company']._company_default_get(
            'purchase.requisition')
        if not pick_in or pick_in.sudo(
        ).warehouse_id.company_id.id != company.id:
            pick_in = self.env['stock.picking.type'].search(
                [('warehouse_id.company_id', '=', company.id),
                 ('code', '=', 'incoming')],
                limit=1,
            )
        return pick_in

    def _get_type_id(self):
        return self.env['purchase.requisition.type'].search([], limit=1)

    name = fields.Char(string='Agreement Reference',
                       required=True,
                       copy=False,
                       default='New',
                       readonly=True)
    origin = fields.Char(string='Source Document')
    order_count = fields.Integer(compute='_compute_orders_number',
                                 string='Number of Orders')
    vendor_id = fields.Many2one('res.partner', string="Vendor")
    type_id = fields.Many2one('purchase.requisition.type',
                              string="Agreement Type",
                              required=True,
                              default=_get_type_id)
    ordering_date = fields.Date(string="Ordering Date",
                                track_visibility='onchange')
    date_end = fields.Datetime(string='Agreement Deadline',
                               track_visibility='onchange')
    schedule_date = fields.Date(
        string='Delivery Date',
        index=True,
        help=
        "The expected and scheduled delivery date where all the products are received",
        track_visibility='onchange')
    user_id = fields.Many2one('res.users',
                              string='Purchase Representative',
                              default=lambda self: self.env.user)
    description = fields.Text()
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 required=True,
                                 default=lambda self: self.env['res.company'].
                                 _company_default_get('purchase.requisition'))
    purchase_ids = fields.One2many('purchase.order',
                                   'requisition_id',
                                   string='Purchase Orders',
                                   states={'done': [('readonly', True)]})
    line_ids = fields.One2many('purchase.requisition.line',
                               'requisition_id',
                               string='Products to Purchase',
                               states={'done': [('readonly', True)]},
                               copy=True)
    warehouse_id = fields.Many2one('stock.warehouse', string='Warehouse')
    state = fields.Selection(PURCHASE_REQUISITION_STATES,
                             'Status',
                             track_visibility='onchange',
                             required=True,
                             copy=False,
                             default='draft')
    state_blanket_order = fields.Selection(PURCHASE_REQUISITION_STATES,
                                           compute='_set_state')
    picking_type_id = fields.Many2one('stock.picking.type',
                                      'Operation Type',
                                      required=True,
                                      default=_get_picking_in)
    is_quantity_copy = fields.Selection(related='type_id.quantity_copy',
                                        readonly=True)
    currency_id = fields.Many2one(
        'res.currency',
        'Currency',
        required=True,
        default=lambda self: self.env.user.company_id.currency_id.id)

    @api.depends('state')
    def _set_state(self):
        for requisition in self:
            requisition.state_blanket_order = requisition.state

    @api.onchange('vendor_id')
    def _onchange_vendor(self):
        if not self.vendor_id:
            self.currency_id = self.env.user.company_id.currency_id.id
        else:
            self.currency_id = self.vendor_id.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id

        requisitions = self.env['purchase.requisition'].search([
            ('vendor_id', '=', self.vendor_id.id),
            ('state', '=', 'ongoing'),
            ('type_id.quantity_copy', '=', 'none'),
        ])
        if any(requisitions):
            title = _("Warning for %s") % self.vendor_id.name
            message = _(
                "There is already an open blanket order for this supplier. We suggest you to use to complete this open blanket order instead of creating a new one."
            )
            warning = {'title': title, 'message': message}
            return {'warning': warning}

    @api.multi
    @api.depends('purchase_ids')
    def _compute_orders_number(self):
        for requisition in self:
            requisition.order_count = len(requisition.purchase_ids)

    @api.multi
    def action_cancel(self):
        # try to set all associated quotations to cancel state
        for requisition in self:
            for requisition_line in requisition.line_ids:
                requisition_line.supplier_info_ids.unlink()
            requisition.purchase_ids.button_cancel()
            for po in requisition.purchase_ids:
                po.message_post(body=_(
                    'Cancelled by the agreement associated to this quotation.')
                                )
        self.write({'state': 'cancel'})

    @api.multi
    def action_in_progress(self):
        self.ensure_one()
        if not all(obj.line_ids for obj in self):
            raise UserError(
                _("You cannot confirm agreement '%s' because there is no product line."
                  ) % self.name)
        if self.type_id.quantity_copy == 'none' and self.vendor_id:
            for requisition_line in self.line_ids:
                if requisition_line.price_unit <= 0.0:
                    raise UserError(
                        _('You cannot confirm the blanket order without price.'
                          ))
                if requisition_line.product_qty <= 0.0:
                    raise UserError(
                        _('You cannot confirm the blanket order without quantity.'
                          ))
                requisition_line.create_supplier_info()
            self.write({'state': 'ongoing'})
        else:
            self.write({'state': 'in_progress'})
        # Set the sequence number regarding the requisition type
        if self.name == 'New':
            if self.is_quantity_copy != 'none':
                self.name = self.env['ir.sequence'].next_by_code(
                    'purchase.requisition.purchase.tender')
            else:
                self.name = self.env['ir.sequence'].next_by_code(
                    'purchase.requisition.blanket.order')

    @api.multi
    def action_open(self):
        self.write({'state': 'open'})

    def action_draft(self):
        self.ensure_one()
        self.name = 'New'
        self.write({'state': 'draft'})

    @api.multi
    def action_done(self):
        """
        Generate all purchase order based on selected lines, should only be called on one agreement at a time
        """
        if any(purchase_order.state in ['draft', 'sent', 'to approve']
               for purchase_order in self.mapped('purchase_ids')):
            raise UserError(
                _('You have to cancel or validate every RfQ before closing the purchase requisition.'
                  ))
        for requisition in self:
            for requisition_line in requisition.line_ids:
                requisition_line.supplier_info_ids.unlink()
        self.write({'state': 'done'})

    def _prepare_tender_values(self, product_id, product_qty, product_uom,
                               location_id, name, origin, values):
        return {
            'origin':
            origin,
            'date_end':
            values['date_planned'],
            'warehouse_id':
            values.get('warehouse_id') and values['warehouse_id'].id or False,
            'company_id':
            values['company_id'].id,
            'line_ids': [(0, 0, {
                'product_id':
                product_id.id,
                'product_uom_id':
                product_uom.id,
                'product_qty':
                product_qty,
                'move_dest_id':
                values.get('move_dest_ids') and values['move_dest_ids'][0].id
                or False,
            })],
        }

    def unlink(self):
        if any(requisition.state not in ('draft', 'cancel')
               for requisition in self):
            raise UserError(_('You can only delete draft requisitions.'))
        # Draft requisitions could have some requisition lines.
        self.mapped('line_ids').unlink()
        return super(PurchaseRequisition, self).unlink()
class PurchaseRequisitionLine(models.Model):
    _name = "purchase.requisition.line"
    _description = "Purchase Requisition Line"
    _rec_name = 'product_id'

    product_id = fields.Many2one('product.product',
                                 string='Product',
                                 domain=[('purchase_ok', '=', True)],
                                 required=True)
    product_uom_id = fields.Many2one('uom.uom',
                                     string='Product Unit of Measure')
    product_qty = fields.Float(
        string='Quantity', digits=dp.get_precision('Product Unit of Measure'))
    price_unit = fields.Float(string='Unit Price',
                              digits=dp.get_precision('Product Price'))
    qty_ordered = fields.Float(compute='_compute_ordered_qty',
                               string='Ordered Quantities')
    requisition_id = fields.Many2one('purchase.requisition',
                                     required=True,
                                     string='Purchase Agreement',
                                     ondelete='cascade')
    company_id = fields.Many2one(
        'res.company',
        related='requisition_id.company_id',
        string='Company',
        store=True,
        readonly=True,
        default=lambda self: self.env['res.company']._company_default_get(
            'purchase.requisition.line'))
    account_analytic_id = fields.Many2one('account.analytic.account',
                                          string='Analytic Account')
    analytic_tag_ids = fields.Many2many('account.analytic.tag',
                                        string='Analytic Tags')
    schedule_date = fields.Date(string='Scheduled Date')
    move_dest_id = fields.Many2one('stock.move', 'Downstream Move')
    supplier_info_ids = fields.One2many('product.supplierinfo',
                                        'purchase_requisition_line_id')

    @api.model
    def create(self, vals):
        res = super(PurchaseRequisitionLine, self).create(vals)
        if res.requisition_id.state not in [
                'draft', 'cancel', 'done'
        ] and res.requisition_id.is_quantity_copy == 'none':
            supplier_infos = self.env['product.supplierinfo'].search([
                ('product_id', '=', vals.get('product_id')),
                ('name', '=', res.requisition_id.vendor_id.id),
            ])
            if not any([s.purchase_requisition_id for s in supplier_infos]):
                res.create_supplier_info()
            if vals['price_unit'] <= 0.0:
                raise UserError(
                    _('You cannot confirm the blanket order without price.'))
        return res

    @api.multi
    def write(self, vals):
        res = super(PurchaseRequisitionLine, self).write(vals)
        if 'price_unit' in vals:
            if vals['price_unit'] <= 0.0 and any(
                    requisition.state not in ['draft', 'cancel', 'done']
                    and requisition.is_quantity_copy == 'none'
                    for requisition in self.mapped('requisition_id')):
                raise UserError(
                    _('You cannot confirm the blanket order without price.'))
            # If the price is updated, we have to update the related SupplierInfo
            self.supplier_info_ids.write({'price': vals['price_unit']})
        return res

    def unlink(self):
        to_unlink = self.filtered(lambda r: r.requisition_id.state not in
                                  ['draft', 'cancel', 'done'])
        to_unlink.mapped('supplier_info_ids').unlink()
        return super(PurchaseRequisitionLine, self).unlink()

    def create_supplier_info(self):
        purchase_requisition = self.requisition_id
        if purchase_requisition.type_id.quantity_copy == 'none' and purchase_requisition.vendor_id:
            # create a supplier_info only in case of blanket order
            self.env['product.supplierinfo'].create({
                'name':
                purchase_requisition.vendor_id.id,
                'product_id':
                self.product_id.id,
                'product_tmpl_id':
                self.product_id.product_tmpl_id.id,
                'price':
                self.price_unit,
                'currency_id':
                self.requisition_id.currency_id.id,
                'purchase_requisition_id':
                purchase_requisition.id,
                'purchase_requisition_line_id':
                self.id,
            })

    @api.multi
    @api.depends('requisition_id.purchase_ids.state')
    def _compute_ordered_qty(self):
        for line in self:
            total = 0.0
            for po in line.requisition_id.purchase_ids.filtered(
                    lambda purchase_order: purchase_order.state in
                ['purchase', 'done']):
                for po_line in po.order_line.filtered(
                        lambda order_line: order_line.product_id == line.
                        product_id):
                    if po_line.product_uom != line.product_uom_id:
                        total += po_line.product_uom._compute_quantity(
                            po_line.product_qty, line.product_uom_id)
                    else:
                        total += po_line.product_qty
            line.qty_ordered = total

    @api.onchange('product_id')
    def _onchange_product_id(self):
        if self.product_id:
            self.product_uom_id = self.product_id.uom_po_id
            self.product_qty = 1.0
        if not self.schedule_date:
            self.schedule_date = self.requisition_id.schedule_date

    @api.multi
    def _prepare_purchase_order_line(self,
                                     name,
                                     product_qty=0.0,
                                     price_unit=0.0,
                                     taxes_ids=False):
        self.ensure_one()
        requisition = self.requisition_id
        if requisition.schedule_date:
            date_planned = datetime.combine(requisition.schedule_date,
                                            time.min)
        else:
            date_planned = datetime.now()
        return {
            'name':
            name,
            'product_id':
            self.product_id.id,
            'product_uom':
            self.product_id.uom_po_id.id,
            'product_qty':
            product_qty,
            'price_unit':
            price_unit,
            'taxes_id': [(6, 0, taxes_ids)],
            'date_planned':
            date_planned,
            'account_analytic_id':
            self.account_analytic_id.id,
            'analytic_tag_ids':
            self.analytic_tag_ids.ids,
            'move_dest_ids':
            self.move_dest_id and [(4, self.move_dest_id.id)] or []
        }
Exemple #26
0
class Employee(models.Model):
    _inherit = "hr.employee"

    remaining_leaves = fields.Float(
        compute='_compute_remaining_leaves',
        string='Remaining Legal Leaves',
        help=
        'Total number of legal leaves allocated to this employee, change this value to create allocation/leave request. '
        'Total based on all the leave types without overriding limit.')
    current_leave_state = fields.Selection(compute='_compute_leave_status',
                                           string="Current Leave Status",
                                           selection=[
                                               ('draft', 'New'),
                                               ('confirm', 'Waiting Approval'),
                                               ('refuse', 'Refused'),
                                               ('validate1',
                                                'Waiting Second Approval'),
                                               ('validate', 'Approved'),
                                               ('cancel', 'Cancelled')
                                           ])
    current_leave_id = fields.Many2one('hr.leave.type',
                                       compute='_compute_leave_status',
                                       string="Current Leave Type")
    leave_date_from = fields.Date('From Date', compute='_compute_leave_status')
    leave_date_to = fields.Date('To Date', compute='_compute_leave_status')
    leaves_count = fields.Float('Number of Leaves',
                                compute='_compute_leaves_count')
    show_leaves = fields.Boolean('Able to see Remaining Leaves',
                                 compute='_compute_show_leaves')
    is_absent_totay = fields.Boolean('Absent Today',
                                     compute='_compute_absent_employee',
                                     search='_search_absent_employee')

    def _get_date_start_work(self):
        return self.create_date

    def _get_remaining_leaves(self):
        """ Helper to compute the remaining leaves for the current employees
            :returns dict where the key is the employee id, and the value is the remain leaves
        """
        self._cr.execute(
            """
            SELECT
                sum(h.number_of_days) AS days,
                h.employee_id
            FROM
                (
                    SELECT holiday_status_id, number_of_days,
                        state, employee_id
                    FROM hr_leave_allocation
                    UNION ALL
                    SELECT holiday_status_id, (number_of_days * -1) as number_of_days,
                        state, employee_id
                    FROM hr_leave
                ) h
                join hr_leave_type s ON (s.id=h.holiday_status_id)
            WHERE
                s.active = true AND h.state='validate' AND
                (s.allocation_type='fixed' OR s.allocation_type='fixed_allocation') AND
                h.employee_id in %s
            GROUP BY h.employee_id""", (tuple(self.ids), ))
        return dict((row['employee_id'], row['days'])
                    for row in self._cr.dictfetchall())

    @api.multi
    def _compute_remaining_leaves(self):
        remaining = self._get_remaining_leaves()
        for employee in self:
            employee.remaining_leaves = float_round(remaining.get(
                employee.id, 0.0),
                                                    precision_digits=2)

    @api.multi
    def _compute_leave_status(self):
        # Used SUPERUSER_ID to forcefully get status of other user's leave, to bypass record rule
        holidays = self.env['hr.leave'].sudo().search([
            ('employee_id', 'in', self.ids),
            ('date_from', '<=', fields.Datetime.now()),
            ('date_to', '>=', fields.Datetime.now()),
            ('holiday_status_id.active', '=', True),
            ('state', 'not in', ('cancel', 'refuse'))
        ])
        leave_data = {}
        for holiday in holidays:
            leave_data[holiday.employee_id.id] = {}
            leave_data[holiday.employee_id.
                       id]['leave_date_from'] = holiday.date_from.date()
            leave_data[holiday.employee_id.
                       id]['leave_date_to'] = holiday.date_to.date()
            leave_data[
                holiday.employee_id.id]['current_leave_state'] = holiday.state
            leave_data[holiday.employee_id.
                       id]['current_leave_id'] = holiday.holiday_status_id.id

        for employee in self:
            employee.leave_date_from = leave_data.get(
                employee.id, {}).get('leave_date_from')
            employee.leave_date_to = leave_data.get(employee.id,
                                                    {}).get('leave_date_to')
            employee.current_leave_state = leave_data.get(
                employee.id, {}).get('current_leave_state')
            employee.current_leave_id = leave_data.get(
                employee.id, {}).get('current_leave_id')

    @api.multi
    def _compute_leaves_count(self):
        all_leaves = self.env['hr.leave.report'].read_group(
            [('employee_id', 'in', self.ids),
             ('holiday_status_id.allocation_type', '!=', 'no'),
             ('holiday_status_id.active', '=', 'True'),
             ('state', '=', 'validate')],
            fields=['number_of_days', 'employee_id'],
            groupby=['employee_id'])
        mapping = dict([(leave['employee_id'][0], leave['number_of_days'])
                        for leave in all_leaves])
        for employee in self:
            employee.leaves_count = float_round(mapping.get(employee.id, 0),
                                                precision_digits=2)

    @api.multi
    def _compute_show_leaves(self):
        show_leaves = self.env['res.users'].has_group(
            'hr_holidays.group_hr_holidays_user')
        for employee in self:
            if show_leaves or employee.user_id == self.env.user:
                employee.show_leaves = True
            else:
                employee.show_leaves = False

    @api.multi
    def _compute_absent_employee(self):
        today_date = datetime.datetime.utcnow().date()
        today_start = fields.Datetime.to_string(
            today_date)  # get the midnight of the current utc day
        today_end = fields.Datetime.to_string(
            today_date + relativedelta(hours=23, minutes=59, seconds=59))
        data = self.env['hr.leave'].read_group(
            [('employee_id', 'in', self.ids),
             ('state', 'not in', ['cancel', 'refuse']),
             ('date_from', '<=', today_end),
             ('date_to', '>=', today_start)], ['employee_id'], ['employee_id'])
        result = dict.fromkeys(self.ids, False)
        for item in data:
            if item['employee_id_count'] >= 1:
                result[item['employee_id'][0]] = True
        for employee in self:
            employee.is_absent_totay = result[employee.id]

    @api.multi
    def _search_absent_employee(self, operator, value):
        today_date = datetime.datetime.utcnow().date()
        today_start = fields.Datetime.to_string(
            today_date)  # get the midnight of the current utc day
        today_end = fields.Datetime.to_string(
            today_date + relativedelta(hours=23, minutes=59, seconds=59))
        holidays = self.env['hr.leave'].sudo().search([
            ('employee_id', '!=', False),
            ('state', 'not in', ['cancel', 'refuse']),
            ('date_from', '<=', today_end), ('date_to', '>=', today_start)
        ])
        return [('id', 'in', holidays.mapped('employee_id').ids)]

    def write(self, values):
        res = super(Employee, self).write(values)
        if 'parent_id' in values or 'department_id' in values:
            hr_vals = {}
            if values.get('parent_id') is not None:
                hr_vals['manager_id'] = values['parent_id']
            if values.get('department_id') is not None:
                hr_vals['department_id'] = values['department_id']
            holidays = self.env['hr.leave'].sudo().search([
                ('state', 'in', ['draft', 'confirm']),
                ('employee_id', 'in', self.ids)
            ])
            holidays.write(hr_vals)
            allocations = self.env['hr.leave.allocation'].sudo().search([
                ('state', 'in', ['draft', 'confirm']),
                ('employee_id', 'in', self.ids)
            ])
            allocations.write(hr_vals)
        return res
Exemple #27
0
class Employee(models.Model):
    _name = "hr.employee"
    _description = "Employee"
    _order = 'name'
    _inherit = ['mail.thread', 'mail.activity.mixin', 'resource.mixin']

    _mail_post_access = 'read'

    @api.model
    def _default_image(self):
        image_path = get_module_resource('hr', 'static/src/img',
                                         'default_image.png')
        return tools.image_resize_image_big(
            base64.b64encode(open(image_path, 'rb').read()))

    # resource and user
    # required on the resource, make sure required="True" set in the view
    name = fields.Char(related='resource_id.name',
                       store=True,
                       oldname='name_related',
                       readonly=False)
    user_id = fields.Many2one('res.users',
                              'User',
                              related='resource_id.user_id',
                              store=True,
                              readonly=False)
    active = fields.Boolean('Active',
                            related='resource_id.active',
                            default=True,
                            store=True,
                            readonly=False)
    # private partner
    address_home_id = fields.Many2one(
        'res.partner',
        'Private Address',
        help=
        'Enter here the private address of the employee, not the one linked to your company.',
        groups="hr.group_hr_user")
    is_address_home_a_company = fields.Boolean(
        'The employee adress has a company linked',
        compute='_compute_is_address_home_a_company',
    )
    country_id = fields.Many2one('res.country',
                                 'Nationality (Country)',
                                 groups="hr.group_hr_user")
    gender = fields.Selection([('male', 'Male'), ('female', 'Female'),
                               ('other', 'Other')],
                              groups="hr.group_hr_user",
                              default="male")
    marital = fields.Selection([('single', 'Single'), ('married', 'Married'),
                                ('cohabitant', 'Legal Cohabitant'),
                                ('widower', 'Widower'),
                                ('divorced', 'Divorced')],
                               string='Marital Status',
                               groups="hr.group_hr_user",
                               default='single')
    spouse_complete_name = fields.Char(string="Spouse Complete Name",
                                       groups="hr.group_hr_user")
    spouse_birthdate = fields.Date(string="Spouse Birthdate",
                                   groups="hr.group_hr_user")
    children = fields.Integer(string='Number of Children',
                              groups="hr.group_hr_user")
    place_of_birth = fields.Char('Place of Birth', groups="hr.group_hr_user")
    country_of_birth = fields.Many2one('res.country',
                                       string="Country of Birth",
                                       groups="hr.group_hr_user")
    birthday = fields.Date('Date of Birth', groups="hr.group_hr_user")
    ssnid = fields.Char('SSN No',
                        help='Social Security Number',
                        groups="hr.group_hr_user")
    sinid = fields.Char('SIN No',
                        help='Social Insurance Number',
                        groups="hr.group_hr_user")
    identification_id = fields.Char(string='Identification No',
                                    groups="hr.group_hr_user")
    passport_id = fields.Char('Passport No', groups="hr.group_hr_user")
    bank_account_id = fields.Many2one(
        'res.partner.bank',
        'Bank Account Number',
        domain="[('partner_id', '=', address_home_id)]",
        groups="hr.group_hr_user",
        help='Employee bank salary account')
    permit_no = fields.Char('Work Permit No', groups="hr.group_hr_user")
    visa_no = fields.Char('Visa No', groups="hr.group_hr_user")
    visa_expire = fields.Date('Visa Expire Date', groups="hr.group_hr_user")
    additional_note = fields.Text(string='Additional Note',
                                  groups="hr.group_hr_user")
    certificate = fields.Selection([
        ('bachelor', 'Bachelor'),
        ('master', 'Master'),
        ('other', 'Other'),
    ],
                                   'Certificate Level',
                                   default='master',
                                   groups="hr.group_hr_user")
    study_field = fields.Char("Field of Study",
                              placeholder='Computer Science',
                              groups="hr.group_hr_user")
    study_school = fields.Char("School", groups="hr.group_hr_user")
    emergency_contact = fields.Char("Emergency Contact",
                                    groups="hr.group_hr_user")
    emergency_phone = fields.Char("Emergency Phone", groups="hr.group_hr_user")
    km_home_work = fields.Integer(string="Km home-work",
                                  groups="hr.group_hr_user")
    google_drive_link = fields.Char(string="Employee Documents",
                                    groups="hr.group_hr_user")
    job_title = fields.Char("Job Title")

    # image: all image fields are base64 encoded and PIL-supported
    image = fields.Binary(
        "Photo",
        default=_default_image,
        attachment=True,
        help=
        "This field holds the image used as photo for the employee, limited to 1024x1024px."
    )
    image_medium = fields.Binary(
        "Medium-sized photo",
        attachment=True,
        help="Medium-sized photo of the employee. It is automatically "
        "resized as a 128x128px image, with aspect ratio preserved. "
        "Use this field in form views or some kanban views.")
    image_small = fields.Binary(
        "Small-sized photo",
        attachment=True,
        help="Small-sized photo of the employee. It is automatically "
        "resized as a 64x64px image, with aspect ratio preserved. "
        "Use this field anywhere a small image is required.")
    # work
    address_id = fields.Many2one('res.partner', 'Work Address')
    work_phone = fields.Char('Work Phone')
    mobile_phone = fields.Char('Work Mobile')
    work_email = fields.Char('Work Email')
    work_location = fields.Char('Work Location')
    # employee in company
    job_id = fields.Many2one('hr.job', 'Job Position')
    department_id = fields.Many2one('hr.department', 'Department')
    parent_id = fields.Many2one('hr.employee', 'Manager')
    child_ids = fields.One2many('hr.employee',
                                'parent_id',
                                string='Subordinates')
    coach_id = fields.Many2one('hr.employee', 'Coach')
    category_ids = fields.Many2many('hr.employee.category',
                                    'employee_category_rel',
                                    'emp_id',
                                    'category_id',
                                    string='Tags')
    # misc
    notes = fields.Text('Notes')
    color = fields.Integer('Color Index', default=0)

    @api.constrains('parent_id')
    def _check_parent_id(self):
        for employee in self:
            if not employee._check_recursion():
                raise ValidationError(
                    _('You cannot create a recursive hierarchy.'))

    @api.onchange('job_id')
    def _onchange_job_id(self):
        if self.job_id:
            self.job_title = self.job_id.name

    @api.onchange('address_id')
    def _onchange_address(self):
        self.work_phone = self.address_id.phone
        self.mobile_phone = self.address_id.mobile

    @api.onchange('company_id')
    def _onchange_company(self):
        address = self.company_id.partner_id.address_get(['default'])
        self.address_id = address['default'] if address else False

    @api.onchange('department_id')
    def _onchange_department(self):
        self.parent_id = self.department_id.manager_id

    @api.onchange('user_id')
    def _onchange_user(self):
        if self.user_id:
            self.update(self._sync_user(self.user_id))

    @api.onchange('resource_calendar_id')
    def _onchange_timezone(self):
        if self.resource_calendar_id and not self.tz:
            self.tz = self.resource_calendar_id.tz

    def _sync_user(self, user):
        vals = dict(
            name=user.name,
            image=user.image,
            work_email=user.email,
        )
        if user.tz:
            vals['tz'] = user.tz
        return vals

    @api.model
    def create(self, vals):
        if vals.get('user_id'):
            vals.update(
                self._sync_user(self.env['res.users'].browse(vals['user_id'])))
        tools.image_resize_images(vals)
        employee = super(Employee, self).create(vals)
        if employee.department_id:
            self.env['mail.channel'].sudo().search([
                ('subscription_department_ids', 'in',
                 employee.department_id.id)
            ])._subscribe_users()
        return employee

    @api.multi
    def write(self, vals):
        if 'address_home_id' in vals:
            account_id = vals.get('bank_account_id') or self.bank_account_id.id
            if account_id:
                self.env['res.partner.bank'].browse(
                    account_id).partner_id = vals['address_home_id']
        if vals.get('user_id'):
            vals.update(
                self._sync_user(self.env['res.users'].browse(vals['user_id'])))
        tools.image_resize_images(vals)
        res = super(Employee, self).write(vals)
        if vals.get('department_id') or vals.get('user_id'):
            department_id = vals['department_id'] if vals.get(
                'department_id') else self[:1].department_id.id
            # When added to a department or changing user, subscribe to the channels auto-subscribed by department
            self.env['mail.channel'].sudo().search([
                ('subscription_department_ids', 'in', department_id)
            ])._subscribe_users()
        return res

    @api.multi
    def unlink(self):
        resources = self.mapped('resource_id')
        super(Employee, self).unlink()
        return resources.unlink()

    @api.depends('address_home_id.parent_id')
    def _compute_is_address_home_a_company(self):
        """Checks that choosen address (res.partner) is not linked to a company.
        """
        for employee in self:
            try:
                employee.is_address_home_a_company = employee.address_home_id.parent_id.id is not False
            except AccessError:
                employee.is_address_home_a_company = False

    @api.model
    def get_import_templates(self):
        return [{
            'label': _('Import Template for Employees'),
            'template': '/hr/static/xls/hr_employee.xls'
        }]
Exemple #28
0
class LandedCost(models.Model):
    _name = 'stock.landed.cost'
    _description = 'Stock Landed Cost'
    _inherit = 'mail.thread'

    name = fields.Char('Name',
                       default=lambda self: _('New'),
                       copy=False,
                       readonly=True,
                       track_visibility='always')
    date = fields.Date('Date',
                       default=fields.Date.context_today,
                       copy=False,
                       required=True,
                       states={'done': [('readonly', True)]},
                       track_visibility='onchange')
    picking_ids = fields.Many2many('stock.picking',
                                   string='Transfers',
                                   copy=False,
                                   states={'done': [('readonly', True)]})
    cost_lines = fields.One2many('stock.landed.cost.lines',
                                 'cost_id',
                                 'Cost Lines',
                                 copy=True,
                                 states={'done': [('readonly', True)]})
    valuation_adjustment_lines = fields.One2many(
        'stock.valuation.adjustment.lines',
        'cost_id',
        'Valuation Adjustments',
        states={'done': [('readonly', True)]})
    description = fields.Text('Item Description',
                              states={'done': [('readonly', True)]})
    amount_total = fields.Float('Total',
                                compute='_compute_total_amount',
                                digits=0,
                                store=True,
                                track_visibility='always')
    state = fields.Selection([('draft', 'Draft'), ('done', 'Posted'),
                              ('cancel', 'Cancelled')],
                             'State',
                             default='draft',
                             copy=False,
                             readonly=True,
                             track_visibility='onchange')
    account_move_id = fields.Many2one('account.move',
                                      'Journal Entry',
                                      copy=False,
                                      readonly=True)
    account_journal_id = fields.Many2one('account.journal',
                                         'Account Journal',
                                         required=True,
                                         states={'done': [('readonly', True)]})
    company_id = fields.Many2one('res.company',
                                 string="Company",
                                 related='account_journal_id.company_id',
                                 readonly=False)

    @api.one
    @api.depends('cost_lines.price_unit')
    def _compute_total_amount(self):
        self.amount_total = sum(line.price_unit for line in self.cost_lines)

    @api.model
    def create(self, vals):
        if vals.get('name', _('New')) == _('New'):
            vals['name'] = self.env['ir.sequence'].next_by_code(
                'stock.landed.cost')
        return super(LandedCost, self).create(vals)

    @api.multi
    def unlink(self):
        self.button_cancel()
        return super(LandedCost, self).unlink()

    @api.multi
    def _track_subtype(self, init_values):
        if 'state' in init_values and self.state == 'done':
            return 'stock_landed_costs.mt_stock_landed_cost_open'
        return super(LandedCost, self)._track_subtype(init_values)

    @api.multi
    def button_cancel(self):
        if any(cost.state == 'done' for cost in self):
            raise UserError(
                _('Validated landed costs cannot be cancelled, but you could create negative landed costs to reverse them'
                  ))
        return self.write({'state': 'cancel'})

    @api.multi
    def button_validate(self):
        if any(cost.state != 'draft' for cost in self):
            raise UserError(_('Only draft landed costs can be validated'))
        if any(not cost.valuation_adjustment_lines for cost in self):
            raise UserError(
                _('No valuation adjustments lines. You should maybe recompute the landed costs.'
                  ))
        if not self._check_sum():
            raise UserError(
                _('Cost and adjustments lines do not match. You should maybe recompute the landed costs.'
                  ))

        for cost in self:
            move = self.env['account.move']
            move_vals = {
                'journal_id': cost.account_journal_id.id,
                'date': cost.date,
                'ref': cost.name,
                'line_ids': [],
            }
            for line in cost.valuation_adjustment_lines.filtered(
                    lambda line: line.move_id):
                # Prorate the value at what's still in stock
                cost_to_add = (
                    line.move_id.remaining_qty /
                    line.move_id.product_qty) * line.additional_landed_cost

                new_landed_cost_value = line.move_id.landed_cost_value + line.additional_landed_cost
                line.move_id.write({
                    'landed_cost_value':
                    new_landed_cost_value,
                    'value':
                    line.move_id.value + line.additional_landed_cost,
                    'remaining_value':
                    line.move_id.remaining_value + cost_to_add,
                    'price_unit':
                    (line.move_id.value + line.additional_landed_cost) /
                    line.move_id.product_qty,
                })
                # `remaining_qty` is negative if the move is out and delivered proudcts that were not
                # in stock.
                qty_out = 0
                if line.move_id._is_in():
                    qty_out = line.move_id.product_qty - line.move_id.remaining_qty
                elif line.move_id._is_out():
                    qty_out = line.move_id.product_qty
                move_vals['line_ids'] += line._create_accounting_entries(
                    move, qty_out)

            move = move.create(move_vals)
            cost.write({'state': 'done', 'account_move_id': move.id})
            move.post()
        return True

    def _check_sum(self):
        """ Check if each cost line its valuation lines sum to the correct amount
        and if the overall total amount is correct also """
        prec_digits = self.env.user.company_id.currency_id.decimal_places
        for landed_cost in self:
            total_amount = sum(
                landed_cost.valuation_adjustment_lines.mapped(
                    'additional_landed_cost'))
            if not tools.float_is_zero(total_amount - landed_cost.amount_total,
                                       precision_digits=prec_digits):
                return False

            val_to_cost_lines = defaultdict(lambda: 0.0)
            for val_line in landed_cost.valuation_adjustment_lines:
                val_to_cost_lines[
                    val_line.cost_line_id] += val_line.additional_landed_cost
            if any(not tools.float_is_zero(cost_line.price_unit - val_amount,
                                           precision_digits=prec_digits)
                   for cost_line, val_amount in val_to_cost_lines.items()):
                return False
        return True

    def get_valuation_lines(self):
        lines = []

        for move in self.mapped('picking_ids').mapped('move_lines'):
            # it doesn't make sense to make a landed cost for a product that isn't set as being valuated in real time at real cost
            if move.product_id.valuation != 'real_time' or move.product_id.cost_method != 'fifo':
                continue
            vals = {
                'product_id': move.product_id.id,
                'move_id': move.id,
                'quantity': move.product_qty,
                'former_cost': move.value,
                'weight': move.product_id.weight * move.product_qty,
                'volume': move.product_id.volume * move.product_qty
            }
            lines.append(vals)

        if not lines and self.mapped('picking_ids'):
            raise UserError(
                _("You cannot apply landed costs on the chosen transfer(s). Landed costs can only be applied for products with automated inventory valuation and FIFO costing method."
                  ))
        return lines

    @api.multi
    def compute_landed_cost(self):
        AdjustementLines = self.env['stock.valuation.adjustment.lines']
        AdjustementLines.search([('cost_id', 'in', self.ids)]).unlink()

        digits = dp.get_precision('Product Price')(self._cr)
        towrite_dict = {}
        for cost in self.filtered(lambda cost: cost.picking_ids):
            total_qty = 0.0
            total_cost = 0.0
            total_weight = 0.0
            total_volume = 0.0
            total_line = 0.0
            all_val_line_values = cost.get_valuation_lines()
            for val_line_values in all_val_line_values:
                for cost_line in cost.cost_lines:
                    val_line_values.update({
                        'cost_id': cost.id,
                        'cost_line_id': cost_line.id
                    })
                    self.env['stock.valuation.adjustment.lines'].create(
                        val_line_values)
                total_qty += val_line_values.get('quantity', 0.0)
                total_weight += val_line_values.get('weight', 0.0)
                total_volume += val_line_values.get('volume', 0.0)

                former_cost = val_line_values.get('former_cost', 0.0)
                # round this because former_cost on the valuation lines is also rounded
                total_cost += tools.float_round(
                    former_cost,
                    precision_digits=digits[1]) if digits else former_cost

                total_line += 1

            for line in cost.cost_lines:
                value_split = 0.0
                for valuation in cost.valuation_adjustment_lines:
                    value = 0.0
                    if valuation.cost_line_id and valuation.cost_line_id.id == line.id:
                        if line.split_method == 'by_quantity' and total_qty:
                            per_unit = (line.price_unit / total_qty)
                            value = valuation.quantity * per_unit
                        elif line.split_method == 'by_weight' and total_weight:
                            per_unit = (line.price_unit / total_weight)
                            value = valuation.weight * per_unit
                        elif line.split_method == 'by_volume' and total_volume:
                            per_unit = (line.price_unit / total_volume)
                            value = valuation.volume * per_unit
                        elif line.split_method == 'equal':
                            value = (line.price_unit / total_line)
                        elif line.split_method == 'by_current_cost_price' and total_cost:
                            per_unit = (line.price_unit / total_cost)
                            value = valuation.former_cost * per_unit
                        else:
                            value = (line.price_unit / total_line)

                        if digits:
                            value = tools.float_round(
                                value,
                                precision_digits=digits[1],
                                rounding_method='UP')
                            fnc = min if line.price_unit > 0 else max
                            value = fnc(value, line.price_unit - value_split)
                            value_split += value

                        if valuation.id not in towrite_dict:
                            towrite_dict[valuation.id] = value
                        else:
                            towrite_dict[valuation.id] += value
        for key, value in towrite_dict.items():
            AdjustementLines.browse(key).write(
                {'additional_landed_cost': value})
        return True
Exemple #29
0
class AccountInvoiceReport(models.Model):
    _name = "account.invoice.report"
    _description = "Invoices Statistics"
    _auto = False
    _rec_name = 'date'

    @api.multi
    @api.depends('currency_id', 'date', 'price_total', 'price_average',
                 'residual')
    def _compute_amounts_in_user_currency(self):
        """Compute the amounts in the currency of the user
        """
        user_currency_id = self.env.user.company_id.currency_id
        currency_rate_id = self.env['res.currency.rate'].search(
            [('rate', '=', 1), '|',
             ('company_id', '=', self.env.user.company_id.id),
             ('company_id', '=', False)],
            limit=1)
        base_currency_id = currency_rate_id.currency_id
        for record in self:
            date = record.date or fields.Date.today()
            company = record.company_id
            record.user_currency_price_total = base_currency_id._convert(
                record.price_total, user_currency_id, company, date)
            record.user_currency_price_average = base_currency_id._convert(
                record.price_average, user_currency_id, company, date)
            record.user_currency_residual = base_currency_id._convert(
                record.residual, user_currency_id, company, date)

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

    _order = 'date desc'

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

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

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

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

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

    @api.model_cr
    def init(self):
        # self._table = account_invoice_report
        tools.drop_view_if_exists(self.env.cr, self._table)
        self.env.cr.execute(
            """CREATE or REPLACE VIEW %s as (
            WITH currency_rate AS (%s)
            %s
            FROM (
                %s %s WHERE ail.account_id IS NOT NULL %s
            ) AS sub
            LEFT JOIN currency_rate cr ON
                (cr.currency_id = sub.currency_id AND
                 cr.company_id = sub.company_id AND
                 cr.date_start <= COALESCE(sub.date, NOW()) AND
                 (cr.date_end IS NULL OR cr.date_end > COALESCE(sub.date, NOW())))
        )""" %
            (self._table, self.env['res.currency']._select_companies_rates(),
             self._select(), self._sub_select(), self._from(),
             self._group_by()))
Exemple #30
0
class ReportMembership(models.Model):
    '''Membership Analysis'''

    _name = 'report.membership'
    _description = 'Membership Analysis'
    _auto = False
    _rec_name = 'start_date'

    start_date = fields.Date(string='Start Date', readonly=True)
    date_to = fields.Date(string='End Date', readonly=True, help="End membership date")
    num_waiting = fields.Integer(string='# Waiting', readonly=True)
    num_invoiced = fields.Integer(string='# Invoiced', readonly=True)
    num_paid = fields.Integer(string='# Paid', readonly=True)
    tot_pending = fields.Float(string='Pending Amount', digits=0, readonly=True)
    tot_earned = fields.Float(string='Earned Amount', digits=0, readonly=True)
    partner_id = fields.Many2one('res.partner', string='Member', readonly=True)
    associate_member_id = fields.Many2one('res.partner', string='Associate Member', readonly=True)
    membership_id = fields.Many2one('product.product', string='Membership Product', readonly=True)
    membership_state = fields.Selection(STATE, string='Current Membership State', readonly=True)
    user_id = fields.Many2one('res.users', string='Salesperson', readonly=True)
    company_id = fields.Many2one('res.company', string='Company', readonly=True)
    quantity = fields.Integer(readonly=True)

    @api.model_cr
    def init(self):
        '''Create the view'''
        tools.drop_view_if_exists(self._cr, self._table)
        self._cr.execute("""
        CREATE OR REPLACE VIEW %s AS (
        SELECT
        MIN(id) AS id,
        partner_id,
        count(membership_id) as quantity,
        user_id,
        membership_state,
        associate_member_id,
        membership_amount,
        date_to,
        start_date,
        COUNT(num_waiting) AS num_waiting,
        COUNT(num_invoiced) AS num_invoiced,
        COUNT(num_paid) AS num_paid,
        SUM(tot_pending) AS tot_pending,
        SUM(tot_earned) AS tot_earned,
        membership_id,
        company_id
        FROM
        (SELECT
            MIN(p.id) AS id,
            p.id AS partner_id,
            p.user_id AS user_id,
            p.membership_state AS membership_state,
            p.associate_member AS associate_member_id,
            p.membership_amount AS membership_amount,
            p.membership_stop AS date_to,
            p.membership_start AS start_date,
            CASE WHEN ml.state = 'waiting'  THEN ml.id END AS num_waiting,
            CASE WHEN ml.state = 'invoiced' THEN ml.id END AS num_invoiced,
            CASE WHEN ml.state = 'paid'     THEN ml.id END AS num_paid,
            CASE WHEN ml.state IN ('waiting', 'invoiced') THEN SUM(il.price_subtotal) ELSE 0 END AS tot_pending,
            CASE WHEN ml.state = 'paid' OR p.membership_state = 'old' THEN SUM(il.price_subtotal) ELSE 0 END AS tot_earned,
            ml.membership_id AS membership_id,
            p.company_id AS company_id
            FROM res_partner p
            LEFT JOIN membership_membership_line ml ON (ml.partner = p.id)
            LEFT JOIN account_invoice_line il ON (ml.account_invoice_line = il.id)
            LEFT JOIN account_invoice ai ON (il.invoice_id = ai.id)
            WHERE p.membership_state != 'none' and p.active = 'true'
            GROUP BY
              p.id,
              p.user_id,
              p.membership_state,
              p.associate_member,
              p.membership_amount,
              p.membership_start,
              ml.membership_id,
              p.company_id,
              ml.state,
              ml.id
        ) AS foo
        GROUP BY
            start_date,
            date_to,
            partner_id,
            user_id,
            membership_id,
            company_id,
            membership_state,
            associate_member_id,
            membership_amount
        )""" % (self._table,))