class AccountCommonReport(models.TransientModel): _name = "account.common.report" _description = "Account Common Report" company_id = fields.Many2one('res.company', string='Company', readonly=True, default=lambda self: self.env.user.company_id) journal_ids = fields.Many2many( 'account.journal', string='Journals', required=True, default=lambda self: self.env['account.journal'].search([])) date_from = fields.Date(string='Start Date') date_to = fields.Date(string='End Date') target_move = fields.Selection([ ('posted', 'All Posted Entries'), ('all', 'All Entries'), ], string='Target Moves', required=True, default='posted') def _build_contexts(self, data): result = {} result['journal_ids'] = 'journal_ids' in data['form'] and data['form'][ 'journal_ids'] or False result['state'] = 'target_move' in data['form'] and data['form'][ 'target_move'] or '' result['date_from'] = data['form']['date_from'] or False result['date_to'] = data['form']['date_to'] or False result['strict_range'] = True if result['date_from'] else False return result def _print_report(self, data): raise (_('Error!'), _('Not implemented.')) @api.multi def check_report(self): self.ensure_one() data = {} data['ids'] = self.env.context.get('active_ids', []) data['model'] = self.env.context.get('active_model', 'ir.ui.menu') data['form'] = self.read( ['date_from', 'date_to', 'journal_ids', 'target_move'])[0] used_context = self._build_contexts(data) data['form']['used_context'] = dict(used_context, lang=self.env.context.get( 'lang', 'en_US')) return self._print_report(data)
class AccountAssetCategory(models.Model): _name = 'account.asset.category' _description = 'Asset category' active = fields.Boolean(default=True) name = fields.Char(required=True, index=True, string="Asset Type") account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account') account_asset_id = fields.Many2one('account.account', string='Asset Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)]) account_income_recognition_id = fields.Many2one('account.account', string='Recognition Income Account', domain=[('internal_type','=','other'), ('deprecated', '=', False)], oldname='account_expense_depreciation_id') account_depreciation_id = fields.Many2one('account.account', string='Depreciation Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)]) journal_id = fields.Many2one('account.journal', string='Journal', required=True) company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env['res.company']._company_default_get('account.asset.category')) method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, default='linear', help="Choose the method to use to compute the amount of depreciation lines.\n" " * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n" " * Degressive: Calculated on basis of: Residual Value * Degressive Factor") method_number = fields.Integer(string='Number of Depreciations', default=5, help="The number of depreciations needed to depreciate your asset") method_period = fields.Integer(string='Period Length', default=1, help="State here the time between 2 depreciations, in months", required=True) method_progress_factor = fields.Float('Degressive Factor', default=0.3) method_time = fields.Selection([('number', 'Number of Depreciations'), ('end', 'Ending Date')], string='Time Method', required=True, default='number', help="Choose the method to use to compute the dates and number of depreciation lines.\n" " * Number of Depreciations: Fix the number of depreciation lines and the time between 2 depreciations.\n" " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.") method_end = fields.Date('Ending date') prorata = fields.Boolean(string='Prorata Temporis', help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first of January') open_asset = fields.Boolean(string='Post Journal Entries', help="Check this if you want to automatically confirm the assets of this category when created by invoices.") type = fields.Selection([('sale', 'Sale: Revenue Recognition'), ('purchase', 'Purchase: Asset')], required=True, index=True, default='purchase') @api.onchange('type') def onchange_type(self): if self.type == 'sale': self.prorata = True self.method_period = 1 else: self.method_period = 12
class LunchCashMove(models.Model): """ Two types of cashmoves: payment (credit) or order (debit) """ _name = 'lunch.cashmove' _description = 'lunch cashmove' user_id = fields.Many2one('res.users', 'User', required=True, default=lambda self: self.env.uid) date = fields.Date('Date', required=True, default=fields.Date.context_today) amount = fields.Float( 'Amount', required=True, help= 'Can be positive (payment) or negative (order or payment if user wants to get his money back)' ) description = fields.Text('Description', help='Can be an order or a payment') order_id = fields.Many2one('lunch.order.line', 'Order', ondelete='cascade') state = fields.Selection([('order', 'Order'), ('payment', 'Payment')], 'Is an order or a payment', default='payment') @api.multi def name_get(self): return [(cashmove.id, '%s %s' % (_('Lunch Cashmove'), '#%d' % cashmove.id)) for cashmove in self]
class AssetDepreciationConfirmationWizard(models.TransientModel): _name = "asset.depreciation.confirmation.wizard" _description = "asset.depreciation.confirmation.wizard" date = fields.Date( 'Account Date', required=True, help= "Choose the period for which you want to automatically post the depreciation lines of running assets", default=fields.Date.context_today) @api.multi def asset_compute(self): self.ensure_one() context = self._context assets = self.env['account.asset.asset'].search([ ('state', '=', 'open'), ('category_id.type', '=', context.get('asset_type')) ]) created_move_ids = assets._compute_entries(self.date) if context.get('asset_type') == 'purchase': title = _('Created Asset Moves') else: title = _('Created Revenue Moves') return { 'name': title, 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.move', 'view_id': False, 'domain': "[('id','in',[" + ','.join(map(str, created_move_ids)) + "])]", 'type': 'ir.actions.act_window', }
class forecast_period(models.Model): _name = 'forecast.period' _rec_name = 'p_date' name = fields.Char('Period Name', edit=False) p_date = fields.Date('Period Date') forecast_id = fields.Many2one('sale.forecast', string='Forecast', edit=False)
class DateRangeGenerator(models.TransientModel): _name = 'date.range.generator' @api.model def _default_company(self): return self.env['res.company']._company_default_get('date.range') name_prefix = fields.Char('Range name prefix', required=True) date_start = fields.Date(strint='Start date', required=True) type_id = fields.Many2one(comodel_name='date.range.type', string='Type', required=True, ondelete='cascade') company_id = fields.Many2one(comodel_name='res.company', string='Company', default=_default_company) unit_of_time = fields.Selection([(YEARLY, 'years'), (MONTHLY, 'months'), (WEEKLY, 'weeks'), (DAILY, 'days')], required=True) duration_count = fields.Integer('Duration', required=True) count = fields.Integer(string="Number of ranges to generate", required=True) @api.multi def _compute_date_ranges(self): self.ensure_one() vals = rrule(freq=self.unit_of_time, interval=self.duration_count, dtstart=fields.Date.from_string(self.date_start), count=self.count + 1) vals = list(vals) date_ranges = [] for idx, dt_start in enumerate(vals[:-1]): date_start = fields.Date.to_string(dt_start.date()) dt_end = vals[idx + 1].date() - relativedelta(days=1) date_end = fields.Date.to_string(dt_end) date_ranges.append({ 'name': '%s-%d' % (self.name_prefix, idx + 1), 'date_start': date_start, 'date_end': date_end, 'type_id': self.type_id.id, 'company_id': self.company_id.id }) return date_ranges @api.multi def action_apply(self): date_ranges = self._compute_date_ranges() if date_ranges: for dr in date_ranges: self.env['date.range'].create(dr) return self.env['ir.actions.act_window'].for_xml_id( module='date_range', xml_id='date_range_action')
class forecast_product(models.Model): _name = 'forecast.product' name = fields.Char('Name') forecast_id = fields.Many2one('sale.forecast', string='Forecast') product_id = fields.Many2one('product.product', string='Product') period_start_date = fields.Date('Start Date') period_end_date = fields.Date('End Date') sales_person = fields.Many2one('res.users', string='Salesperson') # sales_team = fields.Many2one('crm.team', string='Sales Team') forecast_qty = fields.Float('Forecast Qty') onhand_qty = fields.Float('Onhand Qty') rest_period_qty = fields.Float('Rest Period Qty') incoming_qty = fields.Float('Incoming Qty') outgoing_qty = fields.Float('Outgoing Qty') action_qty = fields.Float('Action Qty') document_number = fields.Char('Ref.Doc.No.') procurement_id = fields.Char('Procurement id') action_required = fields.Selection([('buy', 'Buy'), ('manufacture', 'Manufacture'), ('both', 'Both'), ('none', 'Not Required')], 'Action Required', copy=False) last_period_qty = fields.Float('Last Period Qty') sec_last_period_qty = fields.Float('Second Last Period Qty') third_last_period_qty = fields.Float('Third Last Period Qty') avg_qty = fields.Float("Average Forecast Qty") avg_sale_qty = fields.Float("Average Sale Quantity") @api.multi def unlink(self): ''' Here as per the selection of forecast_filter_id the records other than the filter value are not managable as filter is applying on One2many field. so, by default unlink method is called for the records other than the filter records. To resolve this here unlink method is marked as false, as delete functionality has been removed for this perticular field once the record is created. ''' return False
class link_tracker_click(models.Model): _name = "link.tracker.click" _rec_name = "link_id" click_date = fields.Date(string='Create Date') link_id = fields.Many2one('link.tracker', 'Link', required=True, ondelete='cascade') ip = fields.Char(string='Internet Protocol') country_id = fields.Many2one('res.country', 'Country') @api.model def add_click(self, code, ip, country_code, stat_id=False): self = self.sudo() code_rec = self.env['link.tracker.code'].search([('code', '=', code)]) if not code_rec: return None again = self.search_count([('link_id', '=', code_rec.link_id.id), ('ip', '=', ip)]) if not again: country_record = self.env['res.country'].search( [('code', '=', country_code)], limit=1) vals = { 'link_id': code_rec.link_id.id, 'create_date': datetime.date.today(), 'ip': ip, 'country_id': country_record.id, 'mail_stat_id': stat_id } if stat_id: mail_stat = self.env['mail.mail.statistics'].search([ ('id', '=', stat_id) ]) if mail_stat.mass_mailing_campaign_id: vals[ 'mass_mailing_campaign_id'] = mail_stat.mass_mailing_campaign_id.id if mail_stat.mass_mailing_id: vals['mass_mailing_id'] = mail_stat.mass_mailing_id.id self.create(vals)
class AccountMoveLineReconcileWriteoff(models.TransientModel): """ It opens the write off wizard form, in that user can define the journal, account, analytic account for reconcile """ _name = 'account.move.line.reconcile.writeoff' _description = 'Account move line reconcile (writeoff)' journal_id = fields.Many2one('account.journal', string='Write-Off Journal', required=True) writeoff_acc_id = fields.Many2one('account.account', string='Write-Off account', required=True, domain=[('deprecated', '=', False)]) date_p = fields.Date(string='Date', default=fields.Date.context_today) comment = fields.Char(required=True, default='Write-off') analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account') @api.multi def trans_rec_addendum(self): view = self.env.ref('account.account_move_line_reconcile_writeoff') return { 'name': _('Reconcile Writeoff'), 'context': self._context, 'view_type': 'form', 'view_mode': 'form', 'res_model': 'account.move.line.reconcile.writeoff', 'views': [(view.id, 'form')], 'type': 'ir.actions.act_window', 'target': 'new', } @api.multi def trans_rec_reconcile_partial(self): context = self._context or {} self.env['account.move.line'].browse(context.get('active_ids', [])).reconcile() return {'type': 'ir.actions.act_window_close'} @api.multi def trans_rec_reconcile(self): context = dict(self._context or {}) context['date_p'] = self.date_p context['comment'] = self.comment if self.analytic_id: context['analytic_id'] = self.analytic_id.id move_lines = self.env['account.move.line'].browse(self._context.get('active_ids', [])) move_lines.with_context(context).reconcile(self.writeoff_acc_id, self.journal_id) return {'type': 'ir.actions.act_window_close'}
class AccountAgedTrialBalance(models.TransientModel): _name = 'account.aged.trial.balance' _inherit = 'account.common.partner.report' _description = 'Account Aged Trial balance Report' period_length = fields.Integer(string='Period Length (days)', required=True, default=30) journal_ids = fields.Many2many('account.journal', string='Journals', required=True) date_from = fields.Date(default=lambda *a: time.strftime('%Y-%m-%d')) def _print_report(self, data): res = {} data = self.pre_print_report(data) data['form'].update(self.read(['period_length'])[0]) period_length = data['form']['period_length'] if period_length <= 0: raise UserError(_('You must set a period length greater than 0.')) if not data['form']['date_from']: raise UserError(_('You must set a start date.')) start = datetime.strptime(data['form']['date_from'], "%Y-%m-%d") for i in range(5)[::-1]: stop = start - relativedelta(days=period_length) res[str(i)] = { 'name': (i != 0 and (str( (5 - (i + 1)) * period_length) + '-' + str( (5 - i) * period_length)) or ('+' + str(4 * period_length))), 'stop': start.strftime('%Y-%m-%d'), 'start': (i != 0 and stop.strftime('%Y-%m-%d') or False), } start = stop - relativedelta(days=1) data['form'].update(res) return self.env['report'].with_context(landscape=True).get_action( self, 'account.report_agedpartnerbalance', data=data)
class account_analytic_line(models.Model): _name = 'account.analytic.line' _description = 'Analytic Line' _order = 'date desc, id desc' @api.model def _default_user(self): return 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) 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(related='account_id.company_id', string='Company', store=True, readonly=True) currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True)
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'}
class Applicant(models.Model): _name = "hr.applicant" _description = "Applicant" _order = "priority desc, id desc" _inherit = ['mail.thread', 'ir.needaction_mixin', 'utm.mixin'] _mail_mass_mailing = _('Applicants') def _default_stage_id(self): if self._context.get('default_job_id'): ids = self.env['hr.recruitment.stage'].search( [('job_ids', '=', self._context['default_job_id']), ('fold', '=', False)], order='sequence asc', limit=1).ids if ids: return ids[0] return False def _default_company_id(self): company_id = False if self._context.get('default_department_id'): department = self.env['hr.department'].browse( self._context['default_department_id']) company_id = department.company_id.id if not company_id: company_id = self.env['res.company']._company_default_get( 'hr.applicant') return company_id name = fields.Char("Subject / Application Name", required=True) active = fields.Boolean( "Active", default=True, help= "If the active field is set to false, it will allow you to hide the case without removing it." ) description = fields.Text("Description") email_from = fields.Char("Email", size=128, help="These people will receive email.") email_cc = fields.Text( "Watchers Emails", size=252, help= "These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma" ) probability = fields.Float("Probability") partner_id = fields.Many2one('res.partner', "Contact") create_date = fields.Datetime("Creation Date", readonly=True, select=True) write_date = fields.Datetime("Update Date", readonly=True) stage_id = fields.Many2one('hr.recruitment.stage', 'Stage', track_visibility='onchange', domain="[('job_ids', '=', job_id)]", copy=False, select=1, default=_default_stage_id) last_stage_id = fields.Many2one( 'hr.recruitment.stage', "Last Stage", help= "Stage of the applicant before being in the current stage. Used for lost cases analysis." ) categ_ids = fields.Many2many('hr.applicant.category', string="Tags") company_id = fields.Many2one('res.company', "Company", default=_default_company_id) user_id = fields.Many2one('res.users', "Responsible", track_visibility="onchange", default=lambda self: self.env.uid) date_closed = fields.Datetime("Closed", readonly=True, select=True) date_open = fields.Datetime("Assigned", readonly=True, select=True) date_last_stage_update = fields.Datetime("Last Stage Update", select=True, default=fields.Datetime.now) date_action = fields.Date("Next Action Date") title_action = fields.Char("Next Action", size=64) priority = fields.Selection(AVAILABLE_PRIORITIES, "Appreciation", default='0') job_id = fields.Many2one('hr.job', "Applied Job") salary_proposed_extra = fields.Char( "Proposed Salary Extra", help="Salary Proposed by the Organisation, extra advantages") salary_expected_extra = fields.Char( "Expected Salary Extra", help="Salary Expected by Applicant, extra advantages") salary_proposed = fields.Float("Proposed Salary", help="Salary Proposed by the Organisation") salary_expected = fields.Float("Expected Salary", help="Salary Expected by Applicant") availability = fields.Date( "Availability", help= "The date at which the applicant will be available to start working") partner_name = fields.Char("Applicant's Name") partner_phone = fields.Char("Phone", size=32) partner_mobile = fields.Char("Mobile", size=32) type_id = fields.Many2one('hr.recruitment.degree', "Degree") department_id = fields.Many2one('hr.department', "Department") survey = fields.Many2one('survey.survey', related='job_id.survey_id', string="Survey") # TDE FIXME: rename to survey_id response_id = fields.Many2one('survey.user_input', "Response", ondelete="set null", oldname="response") reference = fields.Char("Referred By") day_open = fields.Float(compute='_compute_day', string="Days to Open") day_close = fields.Float(compute='_compute_day', string="Days to Close") color = fields.Integer("Color Index", default=0) emp_id = fields.Many2one('hr.employee', string="Employee", track_visibility="onchange", help="Employee linked to the applicant.") user_email = fields.Char(related='user_id.email', type="char", string="User Email", readonly=True) attachment_number = fields.Integer(compute='_get_attachment_number', string="Number of Attachments") employee_name = fields.Char(related='emp_id.name', string="Employee Name") attachment_ids = fields.One2many('ir.attachment', 'res_id', domain=[('res_model', '=', 'hr.applicant') ], string='Attachments') @api.depends('date_open', 'date_closed') @api.one def _compute_day(self): if self.date_open: date_create = datetime.strptime( self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT) date_open = datetime.strptime(self.date_open, tools.DEFAULT_SERVER_DATETIME_FORMAT) self.day_open = (date_open - date_create).total_seconds() / (24.0 * 3600) if self.date_closed: date_create = datetime.strptime( self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT) date_closed = datetime.strptime( self.date_closed, tools.DEFAULT_SERVER_DATETIME_FORMAT) self.day_close = (date_closed - date_create).total_seconds() / (24.0 * 3600) @api.multi def _get_attachment_number(self): read_group_res = self.env['ir.attachment'].read_group( [('res_model', '=', 'hr.applicant'), ('res_id', 'in', self.ids)], ['res_id'], ['res_id']) attach_data = dict( (res['res_id'], res['res_id_count']) for res in read_group_res) for record in self: record.attachment_number = attach_data.get(record.id, 0) @api.model def _read_group_stage_ids(self, ids, domain, read_group_order=None, access_rights_uid=None): access_rights_uid = access_rights_uid or self.env.uid Stage = self.env['hr.recruitment.stage'] order = Stage._order # lame hack to allow reverting search, should just work in the trivial case if read_group_order == 'stage_id desc': order = "%s desc" % order # retrieve job_id from the context and write the domain: ids + contextual columns (job or default) job_id = self._context.get('default_job_id') department_id = self._context.get('default_department_id') search_domain = [] if job_id: search_domain = [('job_ids', '=', job_id)] if department_id: if search_domain: search_domain = [ '|', ('job_ids.department_id', '=', department_id) ] + search_domain else: search_domain = [('job_ids.department_id', '=', department_id)] if self.ids: if search_domain: search_domain = ['|', ('id', 'in', self.ids)] + search_domain else: search_domain = [('id', 'in', self.ids)] stage_ids = Stage._search(search_domain, order=order, access_rights_uid=access_rights_uid) stages = Stage.sudo(access_rights_uid).browse(stage_ids) result = stages.name_get() # restore order of the search result.sort( lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) fold = {} for stage in stages: fold[stage.id] = stage.fold or False return result, fold _group_by_full = {'stage_id': _read_group_stage_ids} @api.onchange('job_id') def onchange_job_id(self): vals = self._onchange_job_id_internal(self.job_id.id) self.department_id = vals['value']['department_id'] self.user_id = vals['value']['user_id'] self.stage_id = vals['value']['stage_id'] def _onchange_job_id_internal(self, job_id): department_id = False user_id = False stage_id = self.stage_id.id if job_id: job = self.env['hr.job'].browse(job_id) department_id = job.department_id.id user_id = job.user_id.id if not self.stage_id: stage_ids = self.env['hr.recruitment.stage'].search( [('job_ids', '=', job.id), ('fold', '=', False)], order='sequence asc', limit=1).ids stage_id = stage_ids[0] if stage_ids else False return { 'value': { 'department_id': department_id, 'user_id': user_id, 'stage_id': stage_id } } @api.onchange('partner_id') def onchange_partner_id(self): self.partner_phone = self.partner_id.phone self.partner_mobile = self.partner_id.mobile self.email_from = self.partner_id.email @api.onchange('stage_id') def onchange_stage_id(self): vals = self._onchange_stage_id_internal(self.stage_id.id) if vals['value'].get('date_closed'): self.date_closed = vals['value']['date_closed'] def _onchange_stage_id_internal(self, stage_id): if not stage_id: return {'value': {}} stage = self.env['hr.recruitment.stage'].browse(stage_id) if stage.fold: return {'value': {'date_closed': fields.datetime.now()}} return {'value': {'date_closed': False}} @api.model def create(self, vals): if vals.get('department_id' ) and not self._context.get('default_department_id'): self = self.with_context( default_department_id=vals.get('department_id')) if vals.get('job_id') or self._context.get('default_job_id'): job_id = vals.get('job_id') or self._context.get('default_job_id') for key, value in self._onchange_job_id_internal( job_id)['value'].iteritems(): if key not in vals: vals[key] = value if vals.get('user_id'): vals['date_open'] = fields.Datetime.now() if 'stage_id' in vals: vals.update( self._onchange_stage_id_internal( vals.get('stage_id'))['value']) return super(Applicant, self.with_context(mail_create_nolog=True)).create(vals) @api.multi def write(self, vals): # user_id change: update date_open if vals.get('user_id'): vals['date_open'] = fields.Datetime.now() # stage_id: track last stage before update if 'stage_id' in vals: vals['date_last_stage_update'] = fields.Datetime.now() vals.update( self._onchange_stage_id_internal( vals.get('stage_id'))['value']) for applicant in self: vals['last_stage_id'] = applicant.stage_id.id res = super(Applicant, self).write(vals) else: res = super(Applicant, self).write(vals) # post processing: if stage changed, post a message in the chatter if vals.get('stage_id'): if self.stage_id.template_id: self.message_post_with_template(self.stage_id.template_id.id, notify=True, composition_mode='mass_mail') return res @api.model def get_empty_list_help(self, help): return super( Applicant, self.with_context( empty_list_help_model='hr.job', empty_list_help_id=self.env.context.get('default_job_id'), empty_list_help_document_name=_( "job applicants"))).get_empty_list_help(help) @api.multi def action_get_created_employee(self): self.ensure_one() action = self.env['ir.actions.act_window'].for_xml_id( 'hr', 'open_view_employee_list') action['res_id'] = self.mapped('emp_id').ids[0] return action @api.multi def action_makeMeeting(self): """ This opens Meeting's calendar view to schedule meeting on current applicant @return: Dictionary value for created Meeting view """ self.ensure_one() partners = self.partner_id | self.user_id.partner_id | self.department_id.manager_id.user_id.partner_id category = self.env.ref('hr_recruitment.categ_meet_interview') res = self.env['ir.actions.act_window'].for_xml_id( 'calendar', 'action_calendar_event') res['context'] = { 'search_default_partner_ids': self.partner_id.name, 'default_partner_ids': partners.ids, 'default_user_id': self.env.uid, 'default_name': self.name, 'default_categ_ids': category and [category.id] or False, } return res @api.multi def action_start_survey(self): self.ensure_one() # create a response and link it to this applicant if not self.response_id: response = self.env['survey.user_input'].create({ 'survey_id': self.survey.id, 'partner_id': self.partner_id.id }) self.response_id = response.id else: response = self.response_id # grab the token of the response and start surveying return self.survey.with_context( survey_token=response.token).action_start_survey() @api.multi def action_print_survey(self): """ If response is available then print this response otherwise print survey form (print template of the survey) """ self.ensure_one() if not self.response_id: return self.survey.action_print_survey() else: response = self.response_id return self.survey.with_context( survey_token=response.token).action_print_survey() @api.multi def action_get_attachment_tree_view(self): attachment_action = self.env.ref('base.action_attachment') action = attachment_action.read()[0] action['context'] = { 'default_res_model': self._name, 'default_res_id': self.ids[0] } action['domain'] = str( ['&', ('res_model', '=', self._name), ('res_id', 'in', self.ids)]) return action @api.multi def _track_subtype(self, init_values): record = self[0] if 'emp_id' in init_values and record.emp_id: return 'hr_recruitment.mt_applicant_hired' elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence <= 1: return 'hr_recruitment.mt_applicant_new' elif 'stage_id' in init_values and record.stage_id and record.stage_id.sequence > 1: return 'hr_recruitment.mt_applicant_stage_changed' return super(Applicant, self)._track_subtype(init_values) @api.model def message_get_reply_to(self, ids, default=None): """ Override to get the reply_to of the parent project. """ applicants = self.sudo().browse(ids) aliases = self.env['hr.job'].message_get_reply_to( applicants.mapped('job_id').ids, default=default) return dict( (applicant.id, aliases.get(applicant.job_id and applicant.job_id.id or 0, False)) for applicant in applicants) @api.multi def message_get_suggested_recipients(self): recipients = super(Applicant, self).message_get_suggested_recipients() for applicant in self: if applicant.partner_id: applicant._message_add_suggested_recipient( recipients, partner=applicant.partner_id, reason=_('Contact')) elif applicant.email_from: applicant._message_add_suggested_recipient( recipients, email=applicant.email_from, reason=_('Contact Email')) return recipients @api.model def message_new(self, msg, custom_values=None): """ Overrides mail_thread message_new that is called by the mailgateway through message_process. This override updates the document according to the email. """ val = msg.get('from').split('<')[0] defaults = { 'name': msg.get('subject') or _("No Subject"), 'partner_name': val, 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), 'user_id': False, 'partner_id': msg.get('author_id', False), } if msg.get('priority'): defaults['priority'] = msg.get('priority') if custom_values: defaults.update(custom_values) return super(Applicant, self).message_new(msg, custom_values=defaults) @api.multi def create_employee_from_applicant(self): """ Create an hr.employee from the hr.applicants """ employee = False for applicant in self: address_id = contact_name = False if applicant.partner_id: address_id = applicant.partner_id.address_get(['contact' ])['contact'] contact_name = applicant.partner_id.name_get()[0][1] if applicant.job_id and (applicant.partner_name or contact_name): applicant.job_id.write({ 'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1 }) employee = self.env['hr.employee'].create({ 'name': applicant.partner_name or contact_name, 'job_id': applicant.job_id.id, 'address_home_id': address_id, 'department_id': applicant.department_id.id or False, 'address_id': applicant.company_id and applicant.company_id.partner_id and applicant.company_id.partner_id.id or False, 'work_email': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.email or False, 'work_phone': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.phone or False }) applicant.write({'emp_id': employee.id}) applicant.job_id.message_post( body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name, subtype="hr_recruitment.mt_job_applicant_hired") employee._broadcast_welcome() else: raise UserError( _('You must define an Applied Job and a Contact Name for this applicant.' )) employee_action = self.env.ref('hr.open_view_employee_list') dict_act_window = employee_action.read([])[0] if employee: dict_act_window['res_id'] = employee.id dict_act_window['view_mode'] = 'form,tree' return dict_act_window @api.multi def archive_applicant(self): self.write({'active': False}) @api.multi def reset_applicant(self): """ Reinsert the applicant into the recruitment pipe in the first stage""" for applicant in self: first_stage_obj = self.env['hr.recruitment.stage'].search( [('job_ids', 'in', applicant.job_id.id)], order="sequence asc", limit=1) applicant.write({'active': True, 'stage_id': first_stage_obj.id})
class AssetModify(models.TransientModel): _name = 'asset.modify' _description = 'Modify Asset' name = fields.Text(string='Reason', required=True) method_number = fields.Integer(string='Number of Depreciations', required=True) method_period = fields.Integer(string='Period Length') method_end = fields.Date(string='Ending date') asset_method_time = fields.Char(compute='_get_asset_method_time', string='Asset Method Time', readonly=True) @api.one def _get_asset_method_time(self): if self.env.context.get('active_id'): asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id')) self.asset_method_time = asset.method_time @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): result = super(AssetModify, self).fields_view_get(view_id, view_type, toolbar=toolbar, submenu=submenu) asset_id = self.env.context.get('active_id') active_model = self.env.context.get('active_model') if active_model == 'account.asset.asset' and asset_id: asset = self.env['account.asset.asset'].browse(asset_id) doc = etree.XML(result['arch']) if asset.method_time == 'number' and doc.xpath("//field[@name='method_end']"): node = doc.xpath("//field[@name='method_end']")[0] node.set('invisible', '1') setup_modifiers(node, result['fields']['method_end']) elif asset.method_time == 'end' and doc.xpath("//field[@name='method_number']"): node = doc.xpath("//field[@name='method_number']")[0] node.set('invisible', '1') setup_modifiers(node, result['fields']['method_number']) result['arch'] = etree.tostring(doc) return result @api.model def default_get(self, fields): res = super(AssetModify, self).default_get(fields) asset_id = self.env.context.get('active_id') asset = self.env['account.asset.asset'].browse(asset_id) if 'name' in fields: res.update({'name': asset.name}) if 'method_number' in fields and asset.method_time == 'number': res.update({'method_number': asset.method_number}) if 'method_period' in fields: res.update({'method_period': asset.method_period}) if 'method_end' in fields and asset.method_time == 'end': res.update({'method_end': asset.method_end}) if self.env.context.get('active_id'): res['asset_method_time'] = self._get_asset_method_time() return res @api.multi def modify(self): """ Modifies the duration of asset for calculating depreciation and maintains the history of old values, in the chatter. """ asset_id = self.env.context.get('active_id', False) asset = self.env['account.asset.asset'].browse(asset_id) old_values = { 'method_number': asset.method_number, 'method_period': asset.method_period, 'method_end': asset.method_end, } asset_vals = { 'method_number': self.method_number, 'method_period': self.method_period, 'method_end': self.method_end, } asset.write(asset_vals) asset.compute_depreciation_board() tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_period', 'method_end']) changes, tracking_value_ids = asset._message_track(tracked_fields, old_values) if changes: asset.message_post(subject=_('Depreciation board modified'), body=self.name, tracking_value_ids=tracking_value_ids) return {'type': 'ir.actions.act_window_close'}
class DateRange(models.Model): _name = "date.range" _order = "type_name,date_start" @api.model def _default_company(self): return self.env['res.company']._company_default_get('date.range') name = fields.Char(required=True, translate=True) date_start = fields.Date(string='Start date', required=True) date_end = fields.Date(string='End date', required=True) type_id = fields.Many2one(comodel_name='date.range.type', string='Type', select=1, required=True) type_name = fields.Char(string='Type', related='type_id.name', readonly=True, store=True) company_id = fields.Many2one(comodel_name='res.company', string='Company', select=1, default=_default_company) active = fields.Boolean( help="The active field allows you to hide the date range without " "removing it.", default=True) _sql_constraints = [ ('date_range_uniq', 'unique (name,type_id, company_id)', 'A date range must be unique per company !') ] @api.constrains('type_id', 'date_start', 'date_end', 'company_id') def _validate_range(self): for this in self: start = fields.Date.from_string(this.date_start) end = fields.Date.from_string(this.date_end) if start >= end: raise ValidationError( _("%s is not a valid range (%s >= %s)") % (this.name, this.date_start, this.date_end)) if this.type_id.allow_overlap: continue SQL = """ SELECT id FROM date_range dt WHERE DATERANGE(dt.date_start, dt.date_end, '[]') && DATERANGE(%s::date, %s::date, '[]') AND dt.id != %s AND dt.active AND dt.company_id = %s AND dt.type_id=%s;""" self.env.cr.execute(SQL, (this.date_start, this.date_end, this.id, this.company_id.id or None, this.type_id.id)) res = self.env.cr.fetchall() if res: dt = self.browse(res[0][0]) raise ValidationError( _("%s overlaps %s") % (this.name, dt.name)) @api.multi def get_domain(self, field_name): self.ensure_one() return [(field_name, '>=', self.date_start), (field_name, '<=', self.date_end)]
class HrEquipmentRequest(models.Model): _name = 'hr.equipment.request' _inherit = ['mail.thread'] _description = 'Maintenance Requests' @api.returns('self') def _default_employee_get(self): return self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) @api.returns('self') def _default_stage(self): return self.env['hr.equipment.stage'].search([], limit=1) @api.multi def _track_subtype(self, init_values): self.ensure_one() if 'stage_id' in init_values and self.stage_id.sequence <= 1: return 'hr_equipment.mt_req_created' elif 'stage_id' in init_values and self.stage_id.sequence > 1: return 'hr_equipment.mt_req_status' return super(HrEquipmentRequest, self)._track_subtype(init_values) name = fields.Char('Subjects', required=True) description = fields.Text('Description') request_date = fields.Date('Request Date', track_visibility='onchange', default=fields.Date.context_today) employee_id = fields.Many2one('hr.employee', string='Employee', default=_default_employee_get) department_id = fields.Many2one('hr.department', string='Department') category_id = fields.Many2one('hr.equipment.category', string='Category') equipment_id = fields.Many2one('hr.equipment', string='Asset', select=True) user_id = fields.Many2one('res.users', string='Assigned to', track_visibility='onchange') stage_id = fields.Many2one('hr.equipment.stage', string='Stage', track_visibility='onchange', default=_default_stage) priority = fields.Selection([('0', 'Very Low'), ('1', 'Low'), ('2', 'Normal'), ('3', 'High')], string='Priority') color = fields.Integer('Color Index') close_date = fields.Date('Close Date') kanban_state = fields.Selection([('normal', 'In Progress'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')], string='Kanban State', required=True, default='normal', track_visibility='onchange') active = fields.Boolean(default=True, help="Set active to false to hide the maintenance request without deleting it.") @api.multi def archive_equipment_request(self): self.write({'active': False}) @api.multi def reset_equipment_request(self): """ Reinsert the equipment request into the maintenance pipe in the first stage""" first_stage_obj = self.env['hr.equipment.stage'].search([], order="sequence asc", limit=1) self.write({'active': True, 'stage_id': first_stage_obj.id}) @api.onchange('employee_id', 'department_id') def onchange_department_or_employee_id(self): domain = [] if self.department_id: domain = [('department_id', '=', self.department_id.id)] if self.employee_id and self.department_id: domain = ['|'] + domain if self.employee_id: domain = domain + ['|', ('employee_id', '=', self.employee_id.id), ('employee_id', '=', None)] equipment = self.env['hr.equipment'].search(domain, limit=2) if len(equipment) == 1: self.equipment_id = equipment return {'domain': {'equipment_id': domain}} @api.onchange('equipment_id') def onchange_equipment_id(self): self.user_id = self.equipment_id.user_id if self.equipment_id.user_id else self.equipment_id.category_id.user_id self.category_id = self.equipment_id.category_id @api.onchange('category_id') def onchange_category_id(self): if not self.user_id or not self.equipment_id or (self.user_id and not self.equipment_id.user_id): self.user_id = self.category_id.user_id @api.model def create(self, vals): # context: no_log, because subtype already handle this self = self.with_context(mail_create_nolog=True) result = super(HrEquipmentRequest, self).create(vals) if result.employee_id.user_id: result.message_subscribe_users(user_ids=[result.employee_id.user_id.id]) return result @api.multi def write(self, vals): # Overridden to reset the kanban_state to normal whenever # the stage (stage_id) of the Maintenance Request changes. if vals and 'kanban_state' not in vals and 'stage_id' in vals: vals['kanban_state'] = 'normal' if vals.get('employee_id'): employee = self.env['hr.employee'].browse(vals['employee_id']) if employee and employee.user_id: self.message_subscribe_users(user_ids=[employee.user_id.id]) return super(HrEquipmentRequest, self).write(vals) @api.multi def _read_group_stage_ids(self, domain, read_group_order=None, access_rights_uid=None): """ Read group customization in order to display all the stages in the kanban view, even if they are empty """ stage_obj = self.env['hr.equipment.stage'] order = stage_obj._order access_rights_uid = access_rights_uid or self._uid if read_group_order == 'stage_id desc': order = '%s desc' % order stage_ids = stage_obj._search([], order=order, access_rights_uid=access_rights_uid) result = [stage.name_get()[0] for stage in stage_obj.browse(stage_ids)] # restore order of the search result.sort(lambda x, y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0]))) fold = {} for stage in stage_obj.browse(stage_ids): fold[stage.id] = stage.fold or False return result, fold _group_by_full = { 'stage_id': _read_group_stage_ids } @api.model def message_new(self, msg, custom_values=None): """ Overrides mail_thread message_new that is called by the mailgateway through message_process. This override updates the document according to the email. """ if custom_values is None: custom_values = {} email = tools.email_split(msg.get('from')) and tools.email_split(msg.get('from'))[0] or False user = self.env['res.users'].search([('login', '=', email)], limit=1) if user: employee = self.env['hr.employee'].search([('user_id', '=', user.id)], limit=1) if employee: custom_values['employee_id'] = employee and employee[0].id return super(HrEquipmentRequest, self).message_new(msg, custom_values=custom_values)
class HrEquipment(models.Model): _name = 'hr.equipment' _inherit = ['mail.thread'] _description = 'Equipment' @api.multi def _track_subtype(self, init_values): self.ensure_one() if ('employee_id' in init_values and self.employee_id) or ('department_id' in init_values and self.department_id): return 'hr_equipment.mt_mat_assign' return super(HrEquipment, self)._track_subtype(init_values) @api.multi def name_get(self): result = [] for record in self: if record.name and record.serial_no: result.append((record.id, record.name + '/' + record.serial_no)) if record.name and not record.serial_no: result.append((record.id, record.name)) return result @api.model def name_search(self, name, args=None, operator='ilike', limit=100): args = args or [] recs = self.browse() if name: recs = self.search([('name', '=', name)] + args, limit=limit) if not recs: recs = self.search([('name', operator, name)] + args, limit=limit) return recs.name_get() name = fields.Char('Asset Name', required=True, translate=True) user_id = fields.Many2one('res.users', string='Technician', track_visibility='onchange') employee_id = fields.Many2one('hr.employee', string='Assigned to Employee', track_visibility='onchange') department_id = fields.Many2one('hr.department', string='Assigned to Department', track_visibility='onchange') category_id = fields.Many2one('hr.equipment.category', string='Asset Category', track_visibility='onchange') partner_id = fields.Many2one('res.partner', string='Vendor', domain="[('supplier', '=', 1)]") partner_ref = fields.Char('Vendor Reference') location = fields.Char('Location') model = fields.Char('Model') serial_no = fields.Char('Serial Number', copy=False) assign_date = fields.Date('Assigned Date', track_visibility='onchange') cost = fields.Float('Cost') note = fields.Text('Note') warranty = fields.Date('Warranty') color = fields.Integer('Color Index') scrap_date = fields.Date('Scrap Date') equipment_assign_to = fields.Selection( [('department', 'Department'), ('employee', 'Employee')], string='Used By', required=True, default='employee') maintenance_ids = fields.One2many('hr.equipment.request', 'equipment_id') maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Maintenance", store=True) maintenance_open_count = fields.Integer(compute='_compute_maintenance_count', string="Current Maintenance", store=True) @api.one @api.depends('maintenance_ids.stage_id.done') def _compute_maintenance_count(self): self.maintenance_count = len(self.maintenance_ids) self.maintenance_open_count = len(self.maintenance_ids.filtered(lambda x: not x.stage_id.done)) @api.onchange('equipment_assign_to') def _onchange_equipment_assign_to(self): if self.equipment_assign_to == 'employee': self.department_id = False if self.equipment_assign_to == 'department': self.employee_id = False self.assign_date = fields.Date.context_today(self) @api.onchange('category_id') def _onchange_category_id(self): self.user_id = self.category_id.user_id _sql_constraints = [ ('serial_no', 'unique(serial_no)', "Another asset already exists with this serial number!"), ] @api.model def create(self, vals): equipment = super(HrEquipment, self).create(vals) # subscribe employee or department manager when equipment assign to him. user_ids = [] if equipment.employee_id and equipment.employee_id.user_id: user_ids.append(equipment.employee_id.user_id.id) if equipment.department_id and equipment.department_id.manager_id and equipment.department_id.manager_id.user_id: user_ids.append(equipment.department_id.manager_id.user_id.id) if user_ids: equipment.message_subscribe_users(user_ids=user_ids) return equipment @api.multi def write(self, vals): user_ids = [] # subscribe employee or department manager when equipment assign to employee or department. if vals.get('employee_id'): user_id = self.env['hr.employee'].browse(vals['employee_id'])['user_id'] if user_id: user_ids.append(user_id.id) if vals.get('department_id'): department = self.env['hr.department'].browse(vals['department_id']) if department and department.manager_id and department.manager_id.user_id: user_ids.append(department.manager_id.user_id.id) if user_ids: self.message_subscribe_users(user_ids=user_ids) return super(HrEquipment, self).write(vals) @api.multi def _read_group_category_ids(self, domain, read_group_order=None, access_rights_uid=None): """ Read group customization in order to display all the category in the kanban view, even if they are empty """ category_obj = self.env['hr.equipment.category'] order = category_obj._order access_rights_uid = access_rights_uid or self._uid if read_group_order == 'category_id desc': order = '%s desc' % order category_ids = category_obj._search([], order=order, access_rights_uid=access_rights_uid) result = [category.name_get()[0] for category in category_obj.browse(category_ids)] # restore order of the search result.sort(lambda x, y: cmp(category_ids.index(x[0]), category_ids.index(y[0]))) fold = {} for category in category_obj.browse(category_ids): fold[category.id] = category.fold return result, fold _group_by_full = { 'category_id': _read_group_category_ids }
class ir_sequence_date_range(models.Model): _name = 'ir.sequence.date_range' _rec_name = "sequence_id" def _get_number_next_actual(self): '''Return number from ir_sequence row when no_gap implementation, and number from postgres sequence when standard implementation.''' for element in self: if element.sequence_id.implementation != 'standard': element.number_next_actual = element.number_next else: # get number from postgres sequence. Cannot use currval, because that might give an error when # not having used nextval before. query = "SELECT last_value, increment_by, is_called FROM ir_sequence_%03d_%03d" % ( element.sequence_id.id, element.id) self.env.cr.execute(query) (last_value, increment_by, is_called) = self.env.cr.fetchone() if is_called: element.number_next_actual = last_value + increment_by else: element.number_next_actual = last_value def _set_number_next_actual(self): for record in self: record.write({'number_next': record.number_next_actual or 0}) date_from = fields.Date('From', required=True) date_to = fields.Date('To', required=True) sequence_id = fields.Many2one("ir.sequence", 'Main Sequence', required=True, ondelete='cascade') number_next = fields.Integer('Next Number', required=True, default=1, help="Next number of this sequence") number_next_actual = fields.Integer( compute='_get_number_next_actual', inverse='_set_number_next_actual', required=True, string='Next Number', default=1, help="Next number that will be used. This number can be incremented " "frequently so the displayed value might already be obsolete") def _next(self): if self.sequence_id.implementation == 'standard': number_next = _select_nextval( self.env.cr, 'ir_sequence_%03d_%03d' % (self.sequence_id.id, self.id)) else: number_next = _update_nogap(self, self.sequence_id.number_increment) return self.sequence_id.get_next_char(number_next) @api.multi def _alter_sequence(self, number_increment=None, number_next=None): for seq in self: _alter_sequence(self.env.cr, "ir_sequence_%03d_%03d" % (seq.sequence_id.id, seq.id), number_increment=number_increment, number_next=number_next) @api.model def create(self, values): """ Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used. """ seq = super(ir_sequence_date_range, self).create(values) main_seq = seq.sequence_id if main_seq.implementation == 'standard': _create_sequence(self.env.cr, "ir_sequence_%03d_%03d" % (main_seq.id, seq.id), main_seq.number_increment, values.get('number_next_actual', 1)) return seq @api.multi def unlink(self): _drop_sequence( self.env.cr, ["ir_sequence_%03d_%03d" % (x.sequence_id.id, x.id) for x in self]) return super(ir_sequence_date_range, self).unlink() @api.multi def write(self, values): if values.get('number_next'): seq_to_alter = self.filtered( lambda seq: seq.sequence_id.implementation == 'standard') seq_to_alter._alter_sequence(number_next=values.get('number_next')) return super(ir_sequence_date_range, self).write(values)
class AccountInvoiceLine(models.Model): _inherit = 'account.invoice.line' asset_category_id = fields.Many2one('account.asset.category', string='Asset Category') asset_start_date = fields.Date(string='Asset End Date', compute='_get_asset_date', readonly=True, store=True) asset_end_date = fields.Date(string='Asset Start Date', compute='_get_asset_date', readonly=True, store=True) asset_mrr = fields.Float(string='Monthly Recurring Revenue', compute='_get_asset_date', readonly=True, digits=dp.get_precision('Account'), store=True) @api.one @api.depends('asset_category_id', 'invoice_id.date_invoice') def _get_asset_date(self): self.asset_mrr = 0 self.asset_start_date = False self.asset_end_date = False cat = self.asset_category_id if cat: months = cat.method_number * cat.method_period if self.invoice_id.type in ['out_invoice', 'out_refund']: self.asset_mrr = self.price_subtotal_signed / months if self.invoice_id.date_invoice: start_date = datetime.strptime(self.invoice_id.date_invoice, DF).replace(day=1) end_date = (start_date + relativedelta(months=months, days=-1)) self.asset_start_date = start_date.strftime(DF) self.asset_end_date = end_date.strftime(DF) @api.one def asset_create(self): if self.asset_category_id and self.asset_category_id.method_number > 1: vals = { 'name': self.name, 'code': self.invoice_id.number or False, 'category_id': self.asset_category_id.id, 'value': self.price_subtotal, 'partner_id': self.invoice_id.partner_id.id, 'company_id': self.invoice_id.company_id.id, 'currency_id': self.invoice_id.currency_id.id, 'date': self.asset_start_date or self.invoice_id.date_invoice, 'invoice_id': self.invoice_id.id, } changed_vals = self.env[ 'account.asset.asset'].onchange_category_id_values( vals['category_id']) vals.update(changed_vals['value']) asset = self.env['account.asset.asset'].create(vals) if self.asset_category_id.open_asset: asset.validate() return True @api.onchange('product_id') def onchange_product_id(self): if self.product_id: if self.invoice_id.type == 'out_invoice': self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id elif self.invoice_id.type == 'in_invoice': self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id
class HrExpense(models.Model): _name = "hr.expense" _inherit = ['mail.thread', 'ir.needaction_mixin'] _description = "Expense" _order = "date" name = fields.Char(string='Expense Description', readonly=True, required=True, states={'draft': [('readonly', False)]}) date = fields.Date(readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today, string="Date") employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)) product_id = fields.Many2one('product.product', string='Product', readonly=True, states={'draft': [('readonly', False)]}, domain=[('can_be_expensed', '=', True)], required=True) product_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['product.uom'].search([], limit=1, order='id')) unit_amount = fields.Float(string='Unit Price', readonly=True, required=True, states={'draft': [('readonly', False)]}, digits=dp.get_precision('Product Price')) quantity = fields.Float(required=True, readonly=True, states={'draft': [('readonly', False)]}, digits=dp.get_precision('Product Unit of Measure'), default=1) tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id', string='Taxes', states={'done': [('readonly', True)], 'post': [('readonly', True)]}) untaxed_amount = fields.Float(string='Subtotal', store=True, compute='_compute_amount', digits=dp.get_precision('Account')) total_amount = fields.Float(string='Total', store=True, compute='_compute_amount', digits=dp.get_precision('Account')) company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id) currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id) analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account') department_id = fields.Many2one('hr.department', string='Department', states={'post': [('readonly', True)], 'done': [('readonly', True)]}) description = fields.Text() payment_mode = fields.Selection([("own_account", "Employee (to reimburse)"), ("company_account", "Company")], default='own_account', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, string="Payment By") journal_id = fields.Many2one('account.journal', string='Expense Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, default=lambda self: self.env['account.journal'].search([('type', '=', 'purchase')], limit=1), help="The journal used when the expense is done.") bank_journal_id = fields.Many2one('account.journal', string='Bank Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, default=lambda self: self.env['account.journal'].search([('type', 'in', ['case', 'bank'])], limit=1), help="The payment method used when the expense is paid by the company.") account_move_id = fields.Many2one('account.move', string='Journal Entry', copy=False, track_visibility="onchange") attachment_number = fields.Integer(compute='_compute_attachment_number', string='Number of Attachments') state = fields.Selection([('draft', 'To Submit'), ('submit', 'Submitted'), ('approve', 'Approved'), ('post', 'Waiting Payment'), ('done', 'Paid'), ('cancel', 'Refused') ], string='Status', index=True, readonly=True, track_visibility='onchange', copy=False, default='draft', required=True, help='When the expense request is created the status is \'To Submit\'.\n It is submitted by the employee and request is sent to manager, the status is \'Submitted\'.\ \nIf the manager approve it, the status is \'Approved\'.\n If the accountant genrate the accounting entries for the expense request, the status is \'Waiting Payment\'.') @api.depends('quantity', 'unit_amount', 'tax_ids', 'currency_id') def _compute_amount(self): for expense in self: expense.untaxed_amount = expense.unit_amount * expense.quantity taxes = expense.tax_ids.compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id, expense.employee_id.user_id.partner_id) expense.total_amount = taxes.get('total_included') @api.multi def _compute_attachment_number(self): attachment_data = self.env['ir.attachment'].read_group([('res_model', '=', 'hr.expense'), ('res_id', 'in', self.ids)], ['res_id'], ['res_id']) attachment = dict((data['res_id'], data['res_id_count']) for data in attachment_data) for expense in self: expense.attachment_number = attachment.get(expense.id, 0) @api.onchange('product_id') def _onchange_product_id(self): if self.product_id: if not self.name: self.name = self.product_id.display_name or '' self.unit_amount = self.env['product.template']._price_get(self.product_id, 'standard_price')[self.product_id.id] self.product_uom_id = self.product_id.uom_id self.tax_ids = self.product_id.supplier_taxes_id @api.onchange('product_uom_id') def _onchange_product_uom_id(self): if self.product_id and self.product_uom_id.category_id != self.product_id.uom_id.category_id: raise UserError(_('Selected Unit of Measure does not belong to the same category as the product Unit of Measure')) @api.onchange('employee_id') def _onchange_employee_id(self): self.department_id = self.employee_id.department_id def _add_followers(self): user_ids = [] employee = self.employee_id if employee.user_id: user_ids.append(employee.user_id.id) if employee.parent_id: user_ids.append(employee.parent_id.user_id.id) if employee.department_id and employee.department_id.manager_id and employee.parent_id != employee.department_id.manager_id: user_ids.append(employee.department_id.manager_id.user_id.id) self.message_subscribe_users(user_ids=user_ids) @api.model def create(self, vals): hr_expense = super(HrExpense, self).create(vals) if vals.get('employee_id'): hr_expense._add_followers() return hr_expense @api.multi def write(self, vals): res = super(HrExpense, self).write(vals) if vals.get('employee_id'): self._add_followers() return res @api.multi def unlink(self): if any(expense.state not in ['draft', 'cancel'] for expense in self): raise UserError(_('You can only delete draft or refused expenses!')) return super(HrExpense, self).unlink() @api.multi def submit_expenses(self): if any(expense.state != 'draft' for expense in self): raise UserError(_("You can only submit draft expenses!")) self.write({'state': 'submit'}) @api.multi def approve_expenses(self): self.write({'state': 'approve'}) @api.multi def refuse_expenses(self, reason): self.write({'state': 'cancel'}) if self.employee_id.user_id: body = (_("Your Expense %s has been refused.<br/><ul class=o_timeline_tracking_value_list><li>Reason<span> : </span><span class=o_timeline_tracking_value>%s</span></li></ul>") % (self.name, reason)) self.message_post(body=body, partner_ids=[self.employee_id.user_id.partner_id.id]) @api.multi def paid_expenses(self): self.write({'state': 'done'}) @api.multi def reset_expenses(self): return self.write({'state': 'draft'}) @api.multi def _track_subtype(self, init_values): self.ensure_one() if 'state' in init_values and self.state == 'approve': return 'hr_expense.mt_expense_approved' elif 'state' in init_values and self.state == 'submit': return 'hr_expense.mt_expense_confirmed' elif 'state' in init_values and self.state == 'cancel': return 'hr_expense.mt_expense_refused' return super(HrExpense, self)._track_subtype(init_values) def _prepare_move_line(self, line): ''' This function prepares move line of account.move related to an expense ''' partner_id = self.employee_id.address_home_id.commercial_partner_id.id return { 'date_maturity': line.get('date_maturity'), 'partner_id': partner_id, 'name': line['name'][:64], 'date': self.date, 'debit': line['price'] > 0 and line['price'], 'credit': line['price'] < 0 and -line['price'], 'account_id': line['account_id'], 'analytic_line_ids': line.get('analytic_line_ids'), 'amount_currency': line['price'] > 0 and abs(line.get('amount_currency')) or -abs(line.get('amount_currency')), 'currency_id': line.get('currency_id'), 'tax_line_id': line.get('tax_line_id'), 'ref': line.get('ref'), 'quantity': line.get('quantity',1.00), 'product_id': line.get('product_id'), 'product_uom_id': line.get('uom_id'), 'analytic_account_id': line.get('analytic_account_id'), } @api.multi def _compute_expense_totals(self, company_currency, account_move_lines): ''' internal method used for computation of total amount of an expense in the company currency and in the expense currency, given the account_move_lines that will be created. It also do some small transformations at these account_move_lines (for multi-currency purposes) :param account_move_lines: list of dict :rtype: tuple of 3 elements (a, b ,c) a: total in company currency b: total in hr.expense currency c: account_move_lines potentially modified ''' self.ensure_one() total = 0.0 total_currency = 0.0 for line in account_move_lines: line['currency_id'] = False line['amount_currency'] = False if self.currency_id != company_currency: line['currency_id'] = self.currency_id.id line['amount_currency'] = line['price'] line['price'] = self.currency_id.with_context(date=self.date or fields.Date.context_today(self)).compute(line['price'], company_currency) total -= line['price'] total_currency -= line['amount_currency'] or line['price'] return total, total_currency, account_move_lines @api.multi def action_move_create(self): ''' main function that is called when trying to create the accounting entries related to an expense ''' if any(expense.state != 'approve' for expense in self): raise UserError(_("You can only generate accounting entry for approved expense(s).")) if any(expense.employee_id != self[0].employee_id for expense in self): raise UserError(_("Expenses must belong to the same Employee.")) if any(not expense.journal_id for expense in self): raise UserError(_("Expenses must have an expense journal specified to generate accounting entries.")) journal_dict = {} for expense in self: if expense.journal_id not in journal_dict: journal_dict[expense.journal_id] = [] journal_dict[expense.journal_id].append(expense) for journal, expense_list in journal_dict.items(): #create the move that will contain the accounting entries move = self.env['account.move'].create({ 'journal_id': journal.id, 'company_id': self.env.user.company_id.id, }) for expense in expense_list: company_currency = expense.company_id.currency_id diff_currency_p = expense.currency_id != company_currency #one account.move.line per expense (+taxes..) move_lines = expense._move_line_get() #create one more move line, a counterline for the total on payable account total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines) if expense.payment_mode == 'company_account': if not expense.bank_journal_id.default_credit_account_id: raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.bank_journal_id.name)) emp_account = expense.bank_journal_id.default_credit_account_id.id else: if not expense.employee_id.address_home_id: raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name)) emp_account = expense.employee_id.address_home_id.property_account_payable_id.id move_lines.append({ 'type': 'dest', 'name': '/', 'price': total, 'account_id': emp_account, 'date_maturity': expense.date, 'amount_currency': diff_currency_p and total_currency or False, 'currency_id': diff_currency_p and expense.currency_id.id or False, 'ref': expense.employee_id.address_home_id.ref or False }) #convert eml into an osv-valid format lines = map(lambda x:(0, 0, expense._prepare_move_line(x)), move_lines) move.write({'line_ids': lines}) expense.write({'account_move_id': move.id, 'state': 'post'}) if expense.payment_mode == 'company_account': expense.paid_expenses() move.post() return True @api.multi def _move_line_get(self): account_move = [] for expense in self: if expense.product_id: account = expense.product_id.product_tmpl_id._get_product_accounts()['expense'] if not account: raise UserError(_("No Expense account found for the product %s (or for it's category), please configure one.") % (expense.product_id.name)) else: account = self.env['ir.property'].with_context(force_company=expense.company_id.id).get('property_account_expense_categ_id', 'product.category') if not account: raise UserError(_('Please configure Default Expense account for Product expense: `property_account_expense_categ_id`.')) move_line = { 'type': 'src', 'name': expense.name.split('\n')[0][:64], 'price_unit': expense.unit_amount, 'quantity': expense.quantity, 'price': expense.total_amount, 'account_id': account.id, 'product_id': expense.product_id.id, 'uom_id': expense.product_uom_id.id, 'analytic_account_id': expense.analytic_account_id.id, } account_move.append(move_line) # Calculate tax lines and adjust base line taxes = expense.tax_ids.compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id) account_move[-1]['price'] = taxes['total_excluded'] account_move[-1]['tax_ids'] = expense.tax_ids.id for tax in taxes['taxes']: account_move.append({ 'type': 'tax', 'name': tax['name'], 'price_unit': tax['amount'], 'quantity': 1, 'price': tax['amount'], 'account_id': tax['account_id'] or move_line['account_id'], 'tax_line_id': tax['id'], }) return account_move @api.multi def action_get_attachment_view(self): self.ensure_one() res = self.env['ir.actions.act_window'].for_xml_id('base', 'action_attachment') res['domain'] = [('res_model', '=', 'hr.expense'), ('res_id', 'in', self.ids)] res['context'] = {'default_res_model': 'hr.expense', 'default_res_id': self.id} return res
class LunchAlert(models.Model): """ Alerts to display during a lunch order. An alert can be specific to a given day, weekly or daily. The alert is displayed from start to end hour. """ _name = 'lunch.alert' _description = 'Lunch Alert' display = fields.Boolean(compute='_compute_display_get') message = fields.Text('Message', required=True) alert_type = fields.Selection([('specific', 'Specific Day'), ('week', 'Every Week'), ('days', 'Every Day')], string='Recurrency', required=True, select=True, default='specific') specific_day = fields.Date('Day', default=fields.Date.context_today) monday = fields.Boolean('Monday') tuesday = fields.Boolean('Tuesday') wednesday = fields.Boolean('Wednesday') thursday = fields.Boolean('Thursday') friday = fields.Boolean('Friday') saturday = fields.Boolean('Saturday') sunday = fields.Boolean('Sunday') start_hour = fields.Float('Between', oldname='active_from', required=True, default=7) end_hour = fields.Float('And', oldname='active_to', required=True, default=23) @api.multi def name_get(self): return [(alert.id, '%s %s' % (_('Alert'), '#%d' % alert.id)) for alert in self] @api.one def _compute_display_get(self): """ This method check if the alert can be displayed today if alert type is specific : compare specific_day(date) with today's date if alert type is week : check today is set as alert (checkbox true) eg. self['monday'] if alert type is day : True return : Message if can_display_alert is True else False """ days_codes = { '0': 'sunday', '1': 'monday', '2': 'tuesday', '3': 'wednesday', '4': 'thursday', '5': 'friday', '6': 'saturday' } can_display_alert = { 'specific': (self.specific_day == fields.Date.context_today(self)), 'week': self[days_codes[datetime.datetime.now().strftime('%w')]], 'days': True } if can_display_alert[self.alert_type]: mynow = fields.Datetime.context_timestamp(self, datetime.datetime.now()) hour_to = int(self.end_hour) min_to = int((self.end_hour - hour_to) * 60) to_alert = datetime.time(hour_to, min_to) hour_from = int(self.start_hour) min_from = int((self.start_hour - hour_from) * 60) from_alert = datetime.time(hour_from, min_from) if from_alert <= mynow.time() <= to_alert: self.display = True else: self.display = False
class ResCompany(models.Model): _inherit = "res.company" #TODO check all the options/fields are in the views (settings + company form view) fiscalyear_last_day = fields.Integer(default=31, required=True) fiscalyear_last_month = fields.Selection([(1, 'January'), (2, 'February'), (3, 'March'), (4, 'April'), (5, 'May'), (6, 'June'), (7, 'July'), (8, 'August'), (9, 'September'), (10, 'October'), (11, 'November'), (12, 'December')], default=12, required=True) period_lock_date = fields.Date( string="Lock Date for Non-Advisers", help= "Only users with the 'Adviser' role can edit accounts prior to and inclusive of this date. Use it for period locking inside an open fiscal year, for example." ) fiscalyear_lock_date = fields.Date( string="Lock Date", help= "No users, including Advisers, can edit accounts prior to and inclusive of this date. Use it for fiscal year locking for example." ) transfer_account_id = fields.Many2one( 'account.account', domain=lambda self: [ ('reconcile', '=', True), ('user_type_id.id', '=', self.env.ref('account.data_account_type_current_assets').id), ('deprecated', '=', False) ], string="Inter-Banks Transfer Account", help= "Intermediary account used when moving money from a liquidity account to another" ) expects_chart_of_accounts = fields.Boolean( string='Expects a Chart of Accounts', default=True) chart_template_id = fields.Many2one( 'account.chart.template', help='The chart template for the company (if any)') bank_account_code_prefix = fields.Char( string='Prefix of the bank accounts', oldname="bank_account_code_char") cash_account_code_prefix = fields.Char( string='Prefix of the cash accounts') accounts_code_digits = fields.Integer( string='Number of digits in an account code') tax_calculation_rounding_method = fields.Selection( [ ('round_per_line', 'Round per Line'), ('round_globally', 'Round Globally'), ], default='round_per_line', string='Tax Calculation Rounding Method', help= "If you select 'Round per Line' : for each tax, the tax amount will first be computed and rounded for each PO/SO/invoice line and then these rounded amounts will be summed, leading to the total amount for that tax. If you select 'Round Globally': for each tax, the tax amount will be computed for each PO/SO/invoice line, then these amounts will be summed and eventually this total tax amount will be rounded. If you sell with tax included, you should choose 'Round per line' because you certainly want the sum of your tax-included line subtotals to be equal to the total amount with taxes." ) paypal_account = fields.Char( string='Paypal Account', size=128, help="Paypal username (usually email) for receiving online payments.") currency_exchange_journal_id = fields.Many2one( 'account.journal', string="Exchange Gain or Loss Journal", domain=[('type', '=', 'general')]) income_currency_exchange_account_id = fields.Many2one( 'account.account', related='currency_exchange_journal_id.default_credit_account_id', string="Gain Exchange Rate Account", domain= "[('internal_type', '=', 'other'), ('deprecated', '=', False), ('company_id', '=', id)]" ) expense_currency_exchange_account_id = fields.Many2one( 'account.account', related='currency_exchange_journal_id.default_debit_account_id', string="Loss Exchange Rate Account", domain= "[('internal_type', '=', 'other'), ('deprecated', '=', False), ('company_id', '=', id)]" ) anglo_saxon_accounting = fields.Boolean( string="Use anglo-saxon accounting") property_stock_account_input_categ_id = fields.Many2one( 'account.account', string="Input Account for Stock Valuation", oldname="property_stock_account_input_categ") property_stock_account_output_categ_id = fields.Many2one( 'account.account', string="Output Account for Stock Valuation", oldname="property_stock_account_output_categ") property_stock_valuation_account_id = fields.Many2one( 'account.account', string="Account Template for Stock Valuation") bank_journal_ids = fields.One2many('account.journal', 'company_id', domain=[('type', '=', 'bank')], string='Bank Journals') overdue_msg = fields.Text(string='Overdue Payments Message', translate=True, default='''Dear Sir/Madam, Our records indicate that some payments on your account are still due. Please find details below. If the amount has already been paid, please disregard this notice. Otherwise, please forward us the total amount stated below. If you have any queries regarding your account, Please contact us. Thank you in advance for your cooperation. Best Regards,''') @api.multi def compute_fiscalyear_dates(self, date): """ Computes the start and end dates of the fiscalyear where the given 'date' belongs to @param date: a datetime object @returns: a dictionary with date_from and date_to """ self = self[0] last_month = self.fiscalyear_last_month last_day = self.fiscalyear_last_day if (date.month < last_month or (date.month == last_month and date.date <= last_day)): date = date.replace(month=last_month, day=last_day) else: date = date.replace(month=last_month, day=last_day, year=date.year + 1) date_to = date date_from = date + timedelta(days=1) date_from = date_from.replace(year=date_from.year - 1) return {'date_from': date_from, 'date_to': date_to} def get_new_account_code(self, current_code, old_prefix, new_prefix, digits): new_prefix_length = len(new_prefix) number = current_code[len(old_prefix):] return new_prefix + '0' * (digits - new_prefix_length - len(number)) + number def reflect_code_prefix_change(self, old_code, new_code, digits): accounts = self.env['account.account'].search( [('code', 'like', old_code), ('internal_type', '=', 'liquidity'), ('company_id', '=', self.id)], order='code asc') for account in accounts: if account.code.startswith(old_code): account.write({ 'code': self.get_new_account_code(account.code, old_code, new_code, digits) }) @api.multi def write(self, values): # Reflect the change on accounts for company in self: digits = values.get( 'accounts_code_digits') or company.accounts_code_digits if values.get('bank_account_code_prefix') or values.get( 'accounts_code_digits'): new_bank_code = values.get( 'bank_account_code_prefix' ) or company.bank_account_code_prefix company.reflect_code_prefix_change( company.bank_account_code_prefix, new_bank_code, digits) if values.get('cash_account_code_prefix') or values.get( 'accounts_code_digits'): new_cash_code = values.get( 'cash_account_code_prefix' ) or company.cash_account_code_prefix company.reflect_code_prefix_change( company.cash_account_code_prefix, new_cash_code, digits) return super(ResCompany, self).write(values)
class LunchOrder(models.Model): """ A lunch order contains one or more lunch order line(s). It is associated to a user for a given date. When creating a lunch order, applicable lunch alerts are displayed. """ _name = 'lunch.order' _description = 'Lunch Order' _order = 'date desc' def _default_previous_order_ids(self): prev_order = self.env['lunch.order.line'].search( [('user_id', '=', self.env.uid)], limit=20, order='id desc') # If we return return prev_order.ids, we will have duplicates (identical orders). # Therefore, this following part removes duplicates based on product_id and note. return {(order.product_id, order.note): order.id for order in prev_order}.values() user_id = fields.Many2one('res.users', 'User', required=True, readonly=True, states={'new': [('readonly', False)]}, default=lambda self: self.env.uid) date = fields.Date('Date', required=True, readonly=True, states={'new': [('readonly', False)]}, default=fields.Date.context_today) order_line_ids = fields.One2many('lunch.order.line', 'order_id', 'Products', ondelete="cascade", readonly=True, copy=True, states={ 'new': [('readonly', False)], False: [('readonly', False)] }) total = fields.Float(compute='_compute_total', string="Total", store=True) state = fields.Selection([('new', 'New'), ('confirmed', 'Received'), ('cancelled', 'Cancelled')], 'Status', readonly=True, index=True, copy=False, default='new', compute='_compute_order_state', store=True) alerts = fields.Text(compute='_compute_alerts_get', string="Alerts") previous_order_ids = fields.Many2many( 'lunch.order.line', compute='_compute_previous_order_ids', default=lambda self: self._default_previous_order_ids()) company_id = fields.Many2one('res.company', related='user_id.company_id', store=True) currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True, store=True) cash_move_balance = fields.Monetary(compute='_compute_cash_move_balance', multi='cash_move_balance') balance_visible = fields.Boolean(compute='_compute_cash_move_balance', multi='cash_move_balance') @api.one @api.depends('order_line_ids') def _compute_total(self): """ get and sum the order lines' price """ self.total = sum(orderline.price for orderline in self.order_line_ids) @api.multi def name_get(self): return [(order.id, '%s %s' % (_('Lunch Order'), '#%d' % order.id)) for order in self] @api.depends('state') def _compute_alerts_get(self): """ get the alerts to display on the order form """ alert_msg = [ alert.message for alert in self.env['lunch.alert'].search([]) if alert.display ] if self.state == 'new': self.alerts = alert_msg and '\n'.join(alert_msg) or False @api.depends('user_id') def _compute_previous_order_ids(self): self.previous_order_ids = self._default_previous_order_ids() @api.one @api.depends('user_id') def _compute_cash_move_balance(self): domain = [('user_id', '=', self.user_id.id)] lunch_cash = self.env['lunch.cashmove'].read_group( domain, ['amount', 'user_id'], ['user_id']) if len(lunch_cash): self.cash_move_balance = lunch_cash[0]['amount'] self.balance_visible = (self.user_id == self.env.user) or self.user_has_groups( 'lunch.group_lunch_manager') @api.one @api.constrains('date') def _check_date(self): """ Prevents the user to create an order in the past """ date_order = datetime.datetime.strptime(self.date, '%Y-%m-%d') date_today = datetime.datetime.strptime( fields.Date.context_today(self), '%Y-%m-%d') if (date_order < date_today): raise UserError(_('The date of your order is in the past.')) @api.one @api.depends('order_line_ids.state') def _compute_order_state(self): """ Update the state of lunch.order based on its orderlines. Here is the logic: - if at least one order line is cancelled, the order is set as cancelled - if no line is cancelled but at least one line is not confirmed, the order is set as new - if all lines are confirmed, the order is set as confirmed """ if not self.order_line_ids: self.state = 'new' else: isConfirmed = True for orderline in self.order_line_ids: if orderline.state == 'cancelled': self.state = 'cancelled' return elif orderline.state == 'confirmed': continue else: isConfirmed = False if isConfirmed: self.state = 'confirmed' else: self.state = 'new' return
class hr_recruitment_report(models.Model): _name = "hr.recruitment.report" _description = "Recruitments Statistics" _auto = False _rec_name = 'date_create' _order = 'date_create desc' user_id = fields.Many2one('res.users', 'User', readonly=True) company_id = fields.Many2one('res.company', 'Company', readonly=True) date_create = fields.Datetime('Create Date', readonly=True) date_last_stage_update = fields.Datetime('Last Stage Update', readonly=True) date_closed = fields.Date('Closed', readonly=True) job_id = fields.Many2one('hr.job', 'Applied Job', readonly=True) stage_id = fields.Many2one('hr.recruitment.stage', 'Stage') type_id = fields.Many2one('hr.recruitment.degree', 'Degree') department_id = fields.Many2one('hr.department', 'Department', readonly=True) priority = fields.Selection(hr_recruitment.AVAILABLE_PRIORITIES, 'Appreciation') salary_prop = fields.Float("Salary Proposed", digits=0) salary_prop_avg = fields.Float("Avg. Proposed Salary", group_operator="avg", digits=0) salary_exp = fields.Float("Salary Expected", digits=0) salary_exp_avg = fields.Float("Avg. Expected Salary", group_operator="avg", digits=0) partner_id = fields.Many2one('res.partner', 'Partner', readonly=True) delay_close = fields.Float( 'Avg. Delay to Close', digits=(16, 2), readonly=True, group_operator="avg", help="Number of Days to close the project issue") last_stage_id = fields.Many2one('hr.recruitment.stage', 'Last Stage') medium_id = fields.Many2one( 'utm.medium', 'Medium', readonly=True, help="This is the method of delivery. Ex: Postcard, Email, or Banner Ad" ) source_id = fields.Many2one( 'utm.source', 'Source', readonly=True, help= "This is the source of the link Ex: Search Engine, another domain, or name of email list" ) def init(self, cr): tools.drop_view_if_exists(cr, 'hr_recruitment_report') cr.execute(""" create or replace view hr_recruitment_report as ( select min(s.id) as id, s.create_date as date_create, date(s.date_closed) as date_closed, s.date_last_stage_update as date_last_stage_update, s.partner_id, s.company_id, s.user_id, s.job_id, s.type_id, s.department_id, s.priority, s.stage_id, s.last_stage_id, s.medium_id, s.source_id, sum(salary_proposed) as salary_prop, (sum(salary_proposed)/count(*)) as salary_prop_avg, sum(salary_expected) as salary_exp, (sum(salary_expected)/count(*)) as salary_exp_avg, extract('epoch' from (s.write_date-s.create_date))/(3600*24) as delay_close, count(*) as nbr from hr_applicant s group by s.date_open, s.create_date, s.write_date, s.date_closed, s.date_last_stage_update, s.partner_id, s.company_id, s.user_id, s.stage_id, s.last_stage_id, s.type_id, s.priority, s.job_id, s.department_id, s.medium_id, s.source_id ) """)
class AccountAssetDepreciationLine(models.Model): _name = 'account.asset.depreciation.line' _description = 'Asset depreciation line' name = fields.Char(string='Depreciation Name', required=True, index=True) sequence = fields.Integer(required=True) asset_id = fields.Many2one('account.asset.asset', string='Asset', required=True, ondelete='cascade') parent_state = fields.Selection(related='asset_id.state', string='State of Asset') amount = fields.Float(string='Current Depreciation', digits=0, required=True) remaining_value = fields.Float(string='Next Period Depreciation', digits=0, required=True) depreciated_value = fields.Float(string='Cumulative Depreciation', required=True) depreciation_date = fields.Date('Depreciation Date', index=True) move_id = fields.Many2one('account.move', string='Depreciation Entry') move_check = fields.Boolean(compute='_get_move_check', string='Posted', track_visibility='always', store=True) @api.one @api.depends('move_id') def _get_move_check(self): self.move_check = bool(self.move_id) @api.multi def create_move(self, post_move=True): created_moves = self.env['account.move'] for line in self: depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self) company_currency = line.asset_id.company_id.currency_id current_currency = line.asset_id.currency_id amount = company_currency.compute(line.amount, current_currency) sign = (line.asset_id.category_id.journal_id.type == 'purchase' or line.asset_id.category_id.journal_id.type == 'sale' and 1) or -1 asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, line.asset_id.method_number) reference = line.asset_id.code journal_id = line.asset_id.category_id.journal_id.id partner_id = line.asset_id.partner_id.id categ_type = line.asset_id.category_id.type debit_account = line.asset_id.category_id.account_asset_id.id credit_account = line.asset_id.category_id.account_depreciation_id.id move_line_1 = { 'name': asset_name, 'account_id': credit_account, 'debit': 0.0, 'credit': amount, 'journal_id': journal_id, 'partner_id': partner_id, 'currency_id': company_currency != current_currency and current_currency or False, 'amount_currency': company_currency != current_currency and - sign * line.amount or 0.0, 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id if categ_type == 'sale' else False, 'date': depreciation_date, } move_line_2 = { 'name': asset_name, 'account_id': debit_account, 'credit': 0.0, 'debit': amount, 'journal_id': journal_id, 'partner_id': partner_id, 'currency_id': company_currency != current_currency and current_currency or False, 'amount_currency': company_currency != current_currency and sign * line.amount or 0.0, 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id if categ_type == 'purchase' else False, 'date': depreciation_date, } move_vals = { 'ref': reference, 'date': depreciation_date or False, 'journal_id': line.asset_id.category_id.journal_id.id, 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], 'asset_id': line.asset_id.id, } move = self.env['account.move'].create(move_vals) line.write({'move_id': move.id, 'move_check': True}) created_moves |= move if post_move and created_moves: created_moves.post() return [x.id for x in created_moves] @api.multi def post_lines_and_close_asset(self): # we re-evaluate the assets to determine whether we can close them for line in self: line.log_message_when_posted() asset = line.asset_id if asset.currency_id.is_zero(asset.value_residual): asset.message_post(body=_("Document closed.")) asset.write({'state': 'close'}) @api.multi def log_message_when_posted(self): def _format_message(message_description, tracked_values): message = '' if message_description: message = '<span>%s</span>' % message_description for name, values in tracked_values.iteritems(): message += '<div> • <b>%s</b>: ' % name message += '%s</div>' % values return message for line in self: if line.move_id and line.move_id.state == 'draft': partner_name = line.asset_id.partner_id.name currency_name = line.asset_id.currency_id.name msg_values = {_('Currency'): currency_name, _('Amount'): line.amount} if partner_name: msg_values[_('Partner')] = partner_name msg = _format_message(_('Depreciation line posted.'), msg_values) line.asset_id.message_post(body=msg) @api.multi def unlink(self): for record in self: if record.move_check: if record.asset_id.category_id.type == 'purchase': msg = _("You cannot delete posted depreciation lines.") else: msg = _("You cannot delete posted installment lines.") raise UserError(msg) return super(AccountAssetDepreciationLine, self).unlink()
class account_abstract_payment(models.AbstractModel): _name = "account.abstract.payment" _description = "Contains the logic shared between models which allows to register payments" payment_type = fields.Selection([('outbound', 'Send Money'), ('inbound', 'Receive Money')], string='Payment Type', required=True) payment_method_id = fields.Many2one('account.payment.method', string='Payment Type', required=True, oldname="payment_method") payment_method_code = fields.Char( related='payment_method_id.code', help= "Technical field used to adapt the interface to the payment type selected." ) partner_type = fields.Selection([('customer', 'Customer'), ('supplier', 'Vendor')]) partner_id = fields.Many2one('res.partner', string='Partner') amount = fields.Monetary(string='Payment Amount', required=True) currency_id = fields.Many2one( 'res.currency', string='Currency', required=True, default=lambda self: self.env.user.company_id.currency_id) payment_date = fields.Date(string='Payment Date', default=fields.Date.context_today, required=True, copy=False) communication = fields.Char(string='Memo') journal_id = fields.Many2one('account.journal', string='Payment Method', required=True, domain=[('type', 'in', ('bank', 'cash'))]) company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', readonly=True) hide_payment_method = fields.Boolean( compute='_compute_hide_payment_method', help= "Technical field used to hide the payment method if the selected journal has only one available which is 'manual'" ) @api.one @api.constrains('amount') def _check_amount(self): if not self.amount > 0.0: raise ValidationError( 'The payment amount must be strictly positive.') @api.one @api.depends('payment_type', 'journal_id') def _compute_hide_payment_method(self): if not self.journal_id: self.hide_payment_method = True return journal_payment_methods = self.payment_type == 'inbound' and self.journal_id.inbound_payment_method_ids or self.journal_id.outbound_payment_method_ids self.hide_payment_method = len( journal_payment_methods ) == 1 and journal_payment_methods[0].code == 'manual' @api.onchange('journal_id') def _onchange_journal(self): if self.journal_id: self.currency_id = self.journal_id.currency_id or self.company_id.currency_id # Set default payment method (we consider the first to be the default one) payment_methods = self.payment_type == 'inbound' and self.journal_id.inbound_payment_method_ids or self.journal_id.outbound_payment_method_ids self.payment_method_id = payment_methods and payment_methods[ 0] or False # Set payment method domain (restrict to methods enabled for the journal and to selected payment type) payment_type = self.payment_type in ( 'outbound', 'transfer') and 'outbound' or 'inbound' return { 'domain': { 'payment_method_id': [('payment_type', '=', payment_type), ('id', 'in', payment_methods.ids)] } } return {} def _get_invoices(self): """ Return the invoices of the payment. Must be overridden """ raise NotImplementedError def _compute_total_invoices_amount(self): """ Compute the sum of the residual of invoices, expressed in the payment currency """ total = 0 payment_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id for inv in self._get_invoices(): total += inv.residual_company_signed if self.company_id and self.company_id.currency_id != payment_currency: total = self.company_id.currency_id.with_context( date=self.payment_date).compute(total, payment_currency) return abs(total)
class AccountAssetAsset(models.Model): _name = 'account.asset.asset' _description = 'Asset/Revenue Recognition' _inherit = ['mail.thread', 'ir.needaction_mixin'] account_move_ids = fields.One2many('account.move', 'asset_id', string='Entries', readonly=True, states={'draft': [('readonly', False)]}) entry_count = fields.Integer(compute='_entry_count', string='# Asset Entries') name = fields.Char(string='Asset Name', required=True, readonly=True, states={'draft': [('readonly', False)]}) code = fields.Char(string='Reference', size=32, readonly=True, states={'draft': [('readonly', False)]}) value = fields.Float(string='Gross Value', required=True, readonly=True, digits=0, states={'draft': [('readonly', False)]}, oldname='purchase_value') currency_id = fields.Many2one('res.currency', string='Currency', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id.id) company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['res.company']._company_default_get('account.asset.asset')) note = fields.Text() category_id = fields.Many2one('account.asset.category', string='Category', required=True, change_default=True, readonly=True, states={'draft': [('readonly', False)]}) date = fields.Date(string='Date', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today, oldname="purchase_date") state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], 'Status', required=True, copy=False, default='draft', help="When an asset is created, the status is 'Draft'.\n" "If the asset is confirmed, the status goes in 'Running' and the depreciation lines can be posted in the accounting.\n" "You can manually close an asset when the depreciation is over. If the last line of depreciation is posted, the asset automatically goes in that status.") active = fields.Boolean(default=True) partner_id = fields.Many2one('res.partner', string='Partner', readonly=True, states={'draft': [('readonly', False)]}) method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, readonly=True, states={'draft': [('readonly', False)]}, default='linear', help="Choose the method to use to compute the amount of depreciation lines.\n * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n" " * Degressive: Calculated on basis of: Residual Value * Degressive Factor") method_number = fields.Integer(string='Number of Depreciations', readonly=True, states={'draft': [('readonly', False)]}, default=5, help="The number of depreciations needed to depreciate your asset") method_period = fields.Integer(string='Number of Months in a Period', required=True, readonly=True, default=12, states={'draft': [('readonly', False)]}, help="The amount of time between two depreciations, in months") method_end = fields.Date(string='Ending Date', readonly=True, states={'draft': [('readonly', False)]}) method_progress_factor = fields.Float(string='Degressive Factor', readonly=True, default=0.3, states={'draft': [('readonly', False)]}) value_residual = fields.Float(compute='_amount_residual', method=True, digits=0, string='Residual Value') method_time = fields.Selection([('number', 'Number of Depreciations'), ('end', 'Ending Date')], string='Time Method', required=True, readonly=True, default='number', states={'draft': [('readonly', False)]}, help="Choose the method to use to compute the dates and number of depreciation lines.\n" " * Number of Depreciations: Fix the number of depreciation lines and the time between 2 depreciations.\n" " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.") prorata = fields.Boolean(string='Prorata Temporis', readonly=True, states={'draft': [('readonly', False)]}, help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first January / Start date of fiscal year') depreciation_line_ids = fields.One2many('account.asset.depreciation.line', 'asset_id', string='Depreciation Lines', readonly=True, states={'draft': [('readonly', False)], 'open': [('readonly', False)]}) salvage_value = fields.Float(string='Salvage Value', digits=0, readonly=True, states={'draft': [('readonly', False)]}, help="It is the amount you plan to have that you cannot depreciate.") invoice_id = fields.Many2one('account.invoice', string='Invoice', states={'draft': [('readonly', False)]}, copy=False) type = fields.Selection(related="category_id.type", string='Type', required=True) @api.multi def unlink(self): for asset in self: if asset.state in ['open', 'close']: raise UserError(_('You cannot delete a document is in %s state.') % (asset.state,)) if asset.account_move_ids: raise UserError(_('You cannot delete a document that contains posted entries.')) return super(AccountAssetAsset, self).unlink() @api.multi def _get_last_depreciation_date(self): """ @param id: ids of a account.asset.asset objects @return: Returns a dictionary of the effective dates of the last depreciation entry made for given asset ids. If there isn't any, return the purchase date of this asset """ self.env.cr.execute(""" SELECT a.id as id, COALESCE(MAX(m.date),a.date) AS date FROM account_asset_asset a LEFT JOIN account_move m ON (m.asset_id = a.id) WHERE a.id IN %s GROUP BY a.id, a.date """, (tuple(self.ids),)) result = dict(self.env.cr.fetchall()) return result @api.model def _cron_generate_entries(self): assets = self.env['account.asset.asset'].search([('state', '=', 'open')]) assets._compute_entries(datetime.today()) def _compute_board_amount(self, sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date): amount = 0 if sequence == undone_dotation_number: amount = residual_amount else: if self.method == 'linear': amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids)) if self.prorata and self.category_id.type == 'purchase': amount = amount_to_depr / self.method_number days = total_days - float(depreciation_date.strftime('%j')) if sequence == 1: amount = (amount_to_depr / self.method_number) / total_days * days elif sequence == undone_dotation_number: amount = (amount_to_depr / self.method_number) / total_days * (total_days - days) elif self.method == 'degressive': amount = residual_amount * self.method_progress_factor if self.prorata: days = total_days - float(depreciation_date.strftime('%j')) if sequence == 1: amount = (residual_amount * self.method_progress_factor) / total_days * days elif sequence == undone_dotation_number: amount = (residual_amount * self.method_progress_factor) / total_days * (total_days - days) return amount def _compute_board_undone_dotation_nb(self, depreciation_date, total_days): undone_dotation_number = self.method_number if self.method_time == 'end': end_date = datetime.strptime(self.method_end, DF).date() undone_dotation_number = 0 while depreciation_date <= end_date: depreciation_date = date(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+self.method_period) undone_dotation_number += 1 if self.prorata and self.category_id.type == 'purchase': undone_dotation_number += 1 return undone_dotation_number @api.multi def compute_depreciation_board(self): self.ensure_one() posted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: x.move_check) unposted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: not x.move_check) # Remove old unposted depreciation lines. We cannot use unlink() with One2many field commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids] if self.value_residual != 0.0: amount_to_depr = residual_amount = self.value_residual if self.prorata: depreciation_date = datetime.strptime(self._get_last_depreciation_date()[self.id], DF).date() else: # depreciation_date = 1st of January of purchase year asset_date = datetime.strptime(self.date, DF).date() # if we already have some previous validated entries, starting date isn't 1st January but last entry + method period if posted_depreciation_line_ids and posted_depreciation_line_ids[0].depreciation_date: last_depreciation_date = datetime.strptime(posted_depreciation_line_ids[0].depreciation_date, DF).date() depreciation_date = last_depreciation_date + relativedelta(months=+self.method_period) else: depreciation_date = asset_date day = depreciation_date.day month = depreciation_date.month year = depreciation_date.year total_days = (year % 4) and 365 or 366 undone_dotation_number = self._compute_board_undone_dotation_nb(depreciation_date, total_days) for x in range(len(posted_depreciation_line_ids), undone_dotation_number): sequence = x + 1 amount = self._compute_board_amount(sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date) amount = self.currency_id.round(amount) residual_amount -= amount vals = { 'amount': amount, 'asset_id': self.id, 'sequence': sequence, 'name': (self.code or '') + '/' + str(sequence), 'remaining_value': residual_amount, 'depreciated_value': self.value - (self.salvage_value + residual_amount), 'depreciation_date': depreciation_date.strftime(DF), } commands.append((0, False, vals)) # Considering Depr. Period as months depreciation_date = date(year, month, day) + relativedelta(months=+self.method_period) day = depreciation_date.day month = depreciation_date.month year = depreciation_date.year self.write({'depreciation_line_ids': commands}) return True @api.multi def validate(self): self.write({'state': 'open'}) fields = [ 'method', 'method_number', 'method_period', 'method_end', 'method_progress_factor', 'method_time', 'salvage_value', 'invoice_id', ] ref_tracked_fields = self.env['account.asset.asset'].fields_get(fields) for asset in self: tracked_fields = ref_tracked_fields.copy() if asset.method == 'linear': del(tracked_fields['method_progress_factor']) if asset.method_time != 'end': del(tracked_fields['method_end']) else: del(tracked_fields['method_number']) dummy, tracking_value_ids = asset._message_track(tracked_fields, dict.fromkeys(fields)) asset.message_post(subject=_('Asset created'), tracking_value_ids=tracking_value_ids) @api.multi def set_to_close(self): move_ids = [] for asset in self: unposted_depreciation_line_ids = asset.depreciation_line_ids.filtered(lambda x: not x.move_check) if unposted_depreciation_line_ids: old_values = { 'method_end': asset.method_end, 'method_number': asset.method_number, } # Remove all unposted depr. lines commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids] # Create a new depr. line with the residual amount and post it sequence = len(asset.depreciation_line_ids) - len(unposted_depreciation_line_ids) + 1 today = datetime.today().strftime(DF) vals = { 'amount': asset.value_residual, 'asset_id': asset.id, 'sequence': sequence, 'name': (asset.code or '') + '/' + str(sequence), 'remaining_value': 0, 'depreciated_value': asset.value - asset.salvage_value, # the asset is completely depreciated 'depreciation_date': today, } commands.append((0, False, vals)) asset.write({'depreciation_line_ids': commands, 'method_end': today, 'method_number': sequence}) tracked_fields = self.env['account.asset.asset'].fields_get(['method_number', 'method_end']) changes, tracking_value_ids = asset._message_track(tracked_fields, old_values) if changes: asset.message_post(subject=_('Asset sold or disposed. Accounting entry awaiting for validation.'), tracking_value_ids=tracking_value_ids) move_ids += asset.depreciation_line_ids[-1].create_move(post_move=False) if move_ids: name = _('Disposal Move') view_mode = 'form' if len(move_ids) > 1: name = _('Disposal Moves') view_mode = 'tree,form' return { 'name': name, 'view_type': 'form', 'view_mode': view_mode, 'res_model': 'account.move', 'type': 'ir.actions.act_window', 'target': 'current', 'res_id': move_ids[0], } @api.multi def set_to_draft(self): self.write({'state': 'draft'}) @api.one @api.depends('value', 'salvage_value', 'depreciation_line_ids') def _amount_residual(self): total_amount = 0.0 for line in self.depreciation_line_ids: if line.move_check: total_amount += line.amount self.value_residual = self.value - total_amount - self.salvage_value @api.onchange('company_id') def onchange_company_id(self): self.currency_id = self.company_id.currency_id.id @api.multi @api.depends('account_move_ids') def _entry_count(self): for asset in self: asset.entry_count = self.env['account.move'].search_count([('asset_id', '=', asset.id)]) @api.one @api.constrains('prorata', 'method_time') def _check_prorata(self): if self.prorata and self.method_time != 'number': raise ValidationError(_('Prorata temporis can be applied only for time method "number of depreciations".')) @api.onchange('category_id') def onchange_category_id(self): vals = self.onchange_category_id_values(self.category_id.id) # We cannot use 'write' on an object that doesn't exist yet if vals: for k, v in vals['value'].iteritems(): setattr(self, k, v) def onchange_category_id_values(self, category_id): if category_id: category = self.env['account.asset.category'].browse(category_id) return { 'value': { 'method': category.method, 'method_number': category.method_number, 'method_time': category.method_time, 'method_period': category.method_period, 'method_progress_factor': category.method_progress_factor, 'method_end': category.method_end, 'prorata': category.prorata, } } @api.onchange('method_time') def onchange_method_time(self): if self.method_time != 'number': self.prorata = False @api.multi def copy_data(self, default=None): if default is None: default = {} default['name'] = self.name + _(' (copy)') return super(AccountAssetAsset, self).copy_data(default)[0] @api.multi def _compute_entries(self, date): depreciation_ids = self.env['account.asset.depreciation.line'].search([ ('asset_id', 'in', self.ids), ('depreciation_date', '<=', date), ('move_check', '=', False)]) return depreciation_ids.create_move() @api.model def create(self, vals): asset = super(AccountAssetAsset, self.with_context(mail_create_nolog=True)).create(vals) asset.compute_depreciation_board() return asset @api.multi def write(self, vals): res = super(AccountAssetAsset, self).write(vals) if 'depreciation_line_ids' not in vals: self.compute_depreciation_board() return res @api.multi def open_entries(self): return { 'name': _('Journal Entries'), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.move', 'view_id': False, 'type': 'ir.actions.act_window', 'context': dict(self.env.context or {}, search_default_asset_id=self.id, default_asset_id=self.id), }
class report_stock_forecast(models.Model): _name = 'report.stock.forecast' _auto = False date = fields.Date(string='Date') product_id = fields.Many2one('product.product', string='Product', readonly=True) product_tmpl_id = fields.Many2one('product.template', string='Product', related='product_id.product_tmpl_id', readonly=True) cumulative_quantity = fields.Float(string='Cumulative Quantity', readonly=True) quantity = fields.Float(readonly=True) def init(self, cr): tools.drop_view_if_exists(cr, 'report_stock_forecast') cr.execute("""CREATE or REPLACE VIEW report_stock_forecast AS (SELECT MIN(id) as id, product_id as product_id, date as date, sum(product_qty) AS quantity, sum(sum(product_qty)) OVER (PARTITION BY product_id ORDER BY date) AS cumulative_quantity FROM (SELECT MIN(id) as id, MAIN.product_id as product_id, SUB.date as date, CASE WHEN MAIN.date = SUB.date THEN sum(MAIN.product_qty) ELSE 0 END as product_qty FROM (SELECT MIN(sq.id) as id, sq.product_id, date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) as date, SUM(sq.qty) AS product_qty FROM stock_quant as sq LEFT JOIN product_product ON product_product.id = sq.product_id LEFT JOIN stock_location location_id ON sq.location_id = location_id.id WHERE location_id.usage = 'internal' GROUP BY date, sq.product_id UNION ALL SELECT MIN(-sm.id) as id, sm.product_id, CASE WHEN sm.date_expected > CURRENT_DATE THEN date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD')) ELSE date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) END AS date, SUM(sm.product_qty) AS product_qty FROM stock_move as sm LEFT JOIN product_product ON product_product.id = sm.product_id LEFT JOIN stock_location dest_location ON sm.location_dest_id = dest_location.id LEFT JOIN stock_location source_location ON sm.location_id = source_location.id WHERE sm.state IN ('confirmed','assigned','waiting') and source_location.usage != 'internal' and dest_location.usage = 'internal' GROUP BY sm.date_expected,sm.product_id UNION ALL SELECT MIN(-sm.id) as id, sm.product_id, CASE WHEN sm.date_expected > CURRENT_DATE THEN date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD')) ELSE date_trunc('week', to_date(to_char(CURRENT_DATE, 'YYYY/MM/DD'), 'YYYY/MM/DD')) END AS date, SUM(-(sm.product_qty)) AS product_qty FROM stock_move as sm LEFT JOIN product_product ON product_product.id = sm.product_id LEFT JOIN stock_location source_location ON sm.location_id = source_location.id LEFT JOIN stock_location dest_location ON sm.location_dest_id = dest_location.id WHERE sm.state IN ('confirmed','assigned','waiting') and source_location.usage = 'internal' and dest_location.usage != 'internal' GROUP BY sm.date_expected,sm.product_id) as MAIN LEFT JOIN (SELECT DISTINCT date FROM ( SELECT date_trunc('week', CURRENT_DATE) AS DATE UNION ALL SELECT date_trunc('week', to_date(to_char(sm.date_expected, 'YYYY/MM/DD'), 'YYYY/MM/DD')) AS date FROM stock_move sm LEFT JOIN stock_location source_location ON sm.location_id = source_location.id LEFT JOIN stock_location dest_location ON sm.location_dest_id = dest_location.id WHERE sm.state IN ('confirmed','assigned','waiting') and sm.date_expected > CURRENT_DATE and ((dest_location.usage = 'internal' AND source_location.usage != 'internal') or (source_location.usage = 'internal' AND dest_location.usage != 'internal'))) AS DATE_SEARCH) SUB ON (SUB.date IS NOT NULL) GROUP BY MAIN.product_id,SUB.date, MAIN.date ) AS FINAL GROUP BY product_id,date)""")
class event_ticket(models.Model): _name = 'event.event.ticket' _description = 'Event Ticket' name = fields.Char('Name', required=True, translate=True) event_id = fields.Many2one('event.event', "Event", required=True, ondelete='cascade') product_id = fields.Many2one( 'product.product', 'Product', required=True, domain=[("event_type_id", "!=", False)], default=lambda self: self._default_product_id()) registration_ids = fields.One2many('event.registration', 'event_ticket_id', 'Registrations') price = fields.Float('Price', digits=dp.get_precision('Product Price')) deadline = fields.Date("Sales End") is_expired = fields.Boolean('Is Expired', compute='_is_expired') @api.model def _default_product_id(self): try: product = self.env['ir.model.data'].get_object( 'event_sale', 'product_product_event') return product.id except ValueError: return False @api.one @api.depends('deadline') def _is_expired(self): if self.deadline: current_date = fields.Date.context_today( self.with_context({'tz': self.event_id.date_tz})) self.is_expired = self.deadline < current_date else: self.is_expired = False # FIXME non-stored fields wont ends up in _columns (and thus _all_columns), which forbid them # to be used in qweb views. Waiting a fix, we create an old function field directly. """ price_reduce = fields.Float("Price Reduce", compute="_get_price_reduce", store=False, digits=dp.get_precision('Product Price')) @api.one @api.depends('price', 'product_id.lst_price', 'product_id.price') def _get_price_reduce(self): product = self.product_id discount = product.lst_price and (product.lst_price - product.price) / product.lst_price or 0.0 self.price_reduce = (1.0 - discount) * self.price """ def _get_price_reduce(self, cr, uid, ids, field_name, arg, context=None): res = dict.fromkeys(ids, 0.0) for ticket in self.browse(cr, uid, ids, context=context): product = ticket.product_id discount = product.lst_price and ( product.lst_price - product.price) / product.lst_price or 0.0 res[ticket.id] = (1.0 - discount) * ticket.price return res _columns = { 'price_reduce': old_fields.function(_get_price_reduce, type='float', string='Price Reduce', digits_compute=dp.get_precision('Product Price')), } # seats fields seats_availability = fields.Selection([('limited', 'Limited'), ('unlimited', 'Unlimited')], 'Available Seat', required=True, store=True, compute='_compute_seats', default="limited") seats_max = fields.Integer( 'Maximum Available Seats', help= "Define the number of available tickets. If you have too much registrations you will " "not be able to sell tickets anymore. Set 0 to ignore this rule set as unlimited." ) seats_reserved = fields.Integer(string='Reserved Seats', compute='_compute_seats', store=True) seats_available = fields.Integer(string='Available Seats', compute='_compute_seats', store=True) seats_unconfirmed = fields.Integer(string='Unconfirmed Seat Reservations', compute='_compute_seats', store=True) seats_used = fields.Integer(compute='_compute_seats', store=True) @api.multi @api.depends('seats_max', 'registration_ids.state') def _compute_seats(self): """ Determine reserved, available, reserved but unconfirmed and used seats. """ # initialize fields to 0 + compute seats availability for ticket in self: ticket.seats_availability = 'unlimited' if ticket.seats_max == 0 else 'limited' ticket.seats_unconfirmed = ticket.seats_reserved = ticket.seats_used = ticket.seats_available = 0 # aggregate registrations by ticket and by state if self.ids: state_field = { 'draft': 'seats_unconfirmed', 'open': 'seats_reserved', 'done': 'seats_used', } query = """ SELECT event_ticket_id, state, count(event_id) FROM event_registration WHERE event_ticket_id IN %s AND state IN ('draft', 'open', 'done') GROUP BY event_ticket_id, state """ self._cr.execute(query, (tuple(self.ids), )) for event_ticket_id, state, num in self._cr.fetchall(): ticket = self.browse(event_ticket_id) ticket[state_field[state]] += num # compute seats_available for ticket in self: if ticket.seats_max > 0: ticket.seats_available = ticket.seats_max - ( ticket.seats_reserved + ticket.seats_used) @api.one @api.constrains('registration_ids', 'seats_max') def _check_seats_limit(self): if self.seats_max and self.seats_available < 0: raise UserError(_('No more available seats for the ticket')) @api.onchange('product_id') def onchange_product_id(self): price = self.product_id.list_price if self.product_id else 0 return {'value': {'price': price}}
class AccountingReport(models.TransientModel): _name = "accounting.report" _inherit = "account.common.report" _description = "Accounting Report" @api.model def _get_account_report(self): reports = [] if self._context.get('active_id'): menu = self.env['ir.ui.menu'].browse( self._context.get('active_id')).name reports = self.env['account.financial.report'].search([ ('name', 'ilike', menu) ]) return reports and reports[0] or False enable_filter = fields.Boolean(string='Enable Comparison') account_report_id = fields.Many2one('account.financial.report', string='Account Reports', required=True, default=_get_account_report) label_filter = fields.Char( string='Column Label', help= "This label will be displayed on report to show the balance computed for the given comparison filter." ) filter_cmp = fields.Selection([('filter_no', 'No Filters'), ('filter_date', 'Date')], string='Filter by', required=True, default='filter_no') date_from_cmp = fields.Date(string='Start Date') date_to_cmp = fields.Date(string='End Date') debit_credit = fields.Boolean( string='Display Debit/Credit Columns', help= "This option allows you to get more details about the way your balances are computed. Because it is space consuming, we do not allow to use it while doing a comparison." ) def _build_comparison_context(self, data): result = {} result['journal_ids'] = 'journal_ids' in data['form'] and data['form'][ 'journal_ids'] or False result['state'] = 'target_move' in data['form'] and data['form'][ 'target_move'] or '' if data['form']['filter_cmp'] == 'filter_date': result['date_from'] = data['form']['date_from_cmp'] result['date_to'] = data['form']['date_to_cmp'] result['strict_range'] = True return result @api.multi def check_report(self): res = super(AccountingReport, self).check_report() data = {} data['form'] = self.read([ 'account_report_id', 'date_from_cmp', 'date_to_cmp', 'journal_ids', 'filter_cmp', 'target_move' ])[0] for field in ['account_report_id']: if isinstance(data['form'][field], tuple): data['form'][field] = data['form'][field][0] comparison_context = self._build_comparison_context(data) res['data']['form']['comparison_context'] = comparison_context return res def _print_report(self, data): data['form'].update( self.read([ 'date_from_cmp', 'debit_credit', 'date_to_cmp', 'filter_cmp', 'account_report_id', 'enable_filter', 'label_filter', 'target_move' ])[0]) return self.env['report'].get_action(self, 'account.report_financial', data=data)