class Membership(models.Model): _name = 'membership.membership' active = fields.Boolean(string="Active", default=True) name = fields.Char() customer_name = fields.Many2one('res.partner', string="Customer Name", required=True) product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True), ('is_a_membership', '=', True)], required=True) tax_id = fields.Many2many('account.tax', string='Taxes') membership_plan_id = fields.Many2one('membership.plan', string='Membership Plan') trial_period = fields.Boolean(string='Trial period', default=False) trial_duration = fields.Integer(string="Trial Duration", required=True) trial_plan_unit = fields.Selection([('hour', 'Hour(s)'), ('day', 'Day(s)'), ('week', 'week(s)')], string='unit', required=True, default='hour') price = fields.Float(string="Price", required=True) start_date = fields.Date(string="Start Date", readonly=True, track_visibility="onchange") end_date = fields.Date(compute='get_end_date', string="End Date", track_visibility='onchange', store=True) plan_state = fields.Selection([('draft', 'Draft'), ('in_progress', 'In-progress'), ('cancel', 'Cancelled'), ('close', 'Cancelled'), ('expired', 'Expired'), ('renewed', 'Renewed'), ('update', 'Updated'), ('pending', 'Pending')], default='draft', string='State', track_visibility="always", copy=False) auto_renewal = fields.Boolean(string='Auto Renewal', default=False) so_origin = fields.Many2one('sale.order', string="Order Ref") next_payment_date = fields.Datetime(string="Date of Next Payment", copy=False) invoice_ids = fields.Many2many("account.invoice", string='Invoices', readonly=True, copy=False) invoice_count = fields.Integer(compute="get_invoiced_count", readonly=True, string='Invoices') membership_id = fields.Many2one('membership.membership', string="Membership Id", copy=False) source = fields.Selection([('so', 'Sale Order'), ('manual', 'Manual')], 'Related To', default="manual") so_origin = fields.Many2one('sale.order', string="Order Ref") quantity = fields.Float(string='Quantity', required=True, default=1.0) immediate = fields.Boolean(default=True) after = fields.Boolean(default=False) reason = fields.Char(string="Reason", track_visibility="onchange") currency_id = fields.Many2one( 'res.currency', string='Currency', default=lambda self: self.env['website'].get_current_website( ).get_current_pricelist().currency_id.id) last_renewal_date = fields.Date(string="Last renewal Date", readonly=True) menu_ids = fields.Many2many('website.menu', string='membership menu') @api.multi def unlink(self): for current_rec in self: if current_rec.plan_state not in ('draft', 'cancel'): raise UserError( "You can't delete the record because its invoice is create. Try closing it instead" ) super(Membership, current_rec).unlink() return True @api.onchange('product_id') def plan_info(self): if self.product_id: self.membership_plan_id = self.product_id.membership_plan_id.id self.price = self.product_id.lst_price self.member_plan_id = self.product_id.membership_plan_id.id if self.product_id.membership_plan_id.trial_period: self.trial_period = self.product_id.membership_plan_id.trial_period self.trial_duration = self.product_id.membership_plan_id.trial_duration self.trial_plan_unit = self.product_id.membership_plan_id.trial_plan_unit else: self.trial_period = False if self.product_id.membership_plan_id.auto_renewal: self.auto_renewal = self.product_id.membership_plan_id.auto_renewal else: self.auto_renewal = False self.menu_ids = [(6, 0, self.product_id.menu_ids.ids)] def confirm_membership(self): self.update_membership() if (self.plan_state == 'draft'): if self.customer_name.all_membership: self.trial_period = False if self.trial_period: if self.trial_plan_unit == 'hour': add_trial = datetime.timedelta(hours=self.trial_duration) elif self.trial_plan_unit == 'day': add_trial = datetime.timedelta(days=self.trial_duration) elif self.trial_plan_unit == 'week': add_trial = datetime.timedelta(weeks=self.trial_duration) else: add_trial = datetime.timedelta(days=0) self.start_date = (datetime.datetime.today() + add_trial).date() self.next_payment_date = str(self.start_date) self.plan_state = 'in_progress' self.mail_send() self.action_invoice_create() self.assigningpricelist() @api.one @api.depends('start_date') def get_end_date(self): if self.start_date: add_duration = datetime.timedelta(days=1) if self.membership_plan_id.plan_unit == 'day': add_duration = datetime.timedelta( days=self.membership_plan_id.duration) elif self.membership_plan_id.plan_unit == 'month': add_duration = datetime.timedelta( days=(self.membership_plan_id.duration) * 30) elif self.membership_plan_id.plan_unit == 'year': add_duration = datetime.timedelta( days=(self.membership_plan_id.duration) * 365) start_date = datetime.datetime.strptime(str(self.start_date), '%Y-%m-%d') self.end_date = (start_date + add_duration).date() @api.model def create(self, vals): vals['name'] = self.env['ir.sequence'].next_by_code( 'membership.membership') res = super(Membership, self).create(vals) return res def mail_send(self): template_id = self.env.ref( 'website_membership_management.membership_confirm_email') template_id.send_mail(self.id, force_send=True) def reminder_mail_send(self): template_id = self.env.ref( 'website_membership_management.membership_reminder_email') template_id.send_mail(self.id, force_send=True) @api.multi def _prepare_invoice(self): """ Prepare the dict of values to create the new invoice for a sales order. This method may be overridden to implement custom invoice generation (making sure to call super() to establish a clean extension chain). """ self.ensure_one() journal_id = self.env['account.invoice'].default_get(['journal_id' ])['journal_id'] company_id = self.env['account.invoice'].default_get(['company_id' ])['company_id'] name = fiscal_position_id = pos_id = False acc = self.customer_name.property_account_receivable_id.id if not journal_id: raise UserError( _('Please define an accounting sale journal for this company.') ) if self.source in ['api', 'manual']: fiscal_position_id = self.customer_name.property_account_position_id.id invoice_vals = { 'name': self.so_origin.name or '' if self.source == 'so' else name, 'origin': self.name, 'type': 'out_invoice', 'reference': self.membership_plan_id.name or self.name, 'account_id': acc, 'partner_id': self.customer_name.id, 'journal_id': journal_id, 'currency_id': self.so_origin.pricelist_id.currency_id.id or self.currency_id.id or self.env['website'].get_current_website().get_current_pricelist( ).currency_id.id, 'payment_term_id': self.so_origin.payment_term_id.id or '', 'fiscal_position_id': self.so_origin.fiscal_position_id.id or self.so_origin.partner_invoice_id.property_account_position_id.id if self.source == 'so' else fiscal_position_id, 'company_id': self.so_origin.company_id.id if self.source == 'so' else company_id, 'user_id': self.so_origin.user_id and self.so_origin.user_id.id if self.source == 'so' else self._uid, 'date_invoice': str(self.next_payment_date or self.start_date), 'is_a_membership': True, } return invoice_vals @api.model def create_automatic_invoice(self): self.membership_state() memberships = self.search([('start_date', '<=', fields.Datetime.now()), ('plan_state', '=', 'in_progress'), ('auto_renewal', '=', True), ('next_payment_date', '<=', fields.Datetime.now())]) for membership in memberships: membership.action_invoice_create() membership.last_renewal_date = fields.Datetime.now() return True @api.multi def action_invoice_create(self, grouped=False, final=False): inv_obj = self.env['account.invoice'] precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') invoices = {} for membership in self: if not membership.active: raise UserError( _("You can't generate invoice of an Inactive Membership.")) if membership.plan_state == 'draft': raise UserError( "You can't generate invoice of a membership which is in draft state, please confirm it first." ) group_key = membership.id if grouped else ( membership.customer_name.id, membership.product_id.currency_id.id) if group_key not in invoices: inv_data = membership._prepare_invoice() invoice = inv_obj.create(inv_data) invoices[group_key] = invoice elif group_key in invoices and membership.name not in invoices[ group_key].so_origin.split(', '): invoices[group_key].write({ 'origin': invoices[group_key].origin + ', ' + membership.name }) if membership.quantity > 0: membership.invoice_line_create(invoices[group_key].id, membership.quantity) if invoices: message = 'Invoice Created' for inv in invoices.values(): inv.compute_taxes() inv.action_invoice_open() self.invoice_ids = [inv.id for inv in invoices.values()] if self.next_payment_date: next_payment_date = datetime.datetime.strptime( str(self.next_payment_date), '%Y-%m-%d %H:%M:%S') else: start_date = str(self.start_date + " 00:00:00") next_payment_date = datetime.datetime.strptime( start_date, '%Y-%m-%d %H:%M:%S') if self.membership_plan_id.plan_unit == 'day': next_payment_date = next_payment_date + relativedelta( days=self.membership_plan_id.duration) if self.membership_plan_id.plan_unit == 'month': next_payment_date = next_payment_date + relativedelta( months=self.membership_plan_id.duration) if self.membership_plan_id.plan_unit == 'year': next_payment_date = next_payment_date + relativedelta( years=self.membership_plan_id.duration) if self.membership_plan_id.plan_unit == 'hour': next_payment_date = next_payment_date + timedelta( hours=self.membership_plan_id.duration) self.next_payment_date = str(next_payment_date) or False wizard_id = self.env['membership.message.wizard'].create( {'message': message}) else: wizard_id = self.env['membership.message.wizard'].create( {'message': 'Membership Expired.'}) return { 'name': ("Message"), 'view_mode': 'form', 'view_id': False, 'view_type': 'form', 'res_model': 'membership.message.wizard', 'res_id': wizard_id.id, 'type': 'ir.actions.act_window', 'nodestroy': True, 'target': 'new', } @api.multi def _prepare_invoice_line(self, qty): self.ensure_one() if self.auto_renewal: product = self.product_id.with_context( lang=self.customer_name.lang, partner=self.customer_name.id, quantity=self.quantity, date=self.start_date, pricelist=self.membership_plan_id.membership_pricelist.id, uom=self.product_id.uom_po_id.id or False) else: product = self.product_id.with_context( lang=self.customer_name.lang, partner=self.customer_name.id, quantity=self.quantity, date=self.start_date, pricelist=self.so_origin.pricelist_id.id, uom=self.product_id.uom_po_id.id or False) name = product.name_get()[0][1] if product.description_sale: name += '\n' + product.description_sale res = {} account = self.product_id.property_account_income_id or self.product_id.categ_id.property_account_income_categ_id if not account: raise UserError(_('Please define income account for this product: "%s" (id:%d) - or for its category: "%s".') % \ (self.product_id.name, self.product_id.id, self.product_id.categ_id.name)) fpos = self.customer_name.property_account_position_id if fpos: account = fpos.map_account(account) res = { 'name': name, 'origin': self.name, 'account_id': account.id, 'price_unit': self.price, 'quantity': self.quantity, 'product_id': self.product_id.id or False, 'invoice_line_tax_ids': [(6, 0, self.tax_id.ids)], 'pricelist': self.so_origin.pricelist_id, } return res @api.multi def invoice_line_create(self, invoice_id, qty): precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') for line in self: if not float_is_zero(qty, precision_digits=precision): vals = line._prepare_invoice_line(qty=qty) vals.update({'invoice_id': invoice_id}) self.env['account.invoice.line'].create(vals) @api.multi def action_view_invoice(self): invoice_ids = self.mapped('invoice_ids') imd = self.env['ir.model.data'] action = imd.xmlid_to_object('account.action_invoice_tree1') list_view_id = imd.xmlid_to_res_id('account.invoice_tree') form_view_id = imd.xmlid_to_res_id('account.invoice_form') result = { 'name': action.name, 'help': action.help, 'type': action.type, 'views': [[list_view_id, 'tree'], [form_view_id, 'form'], [False, 'graph'], [False, 'kanban'], [False, 'calendar'], [False, 'pivot']], 'target': action.target, 'context': action.context, 'res_model': action.res_model, } if len(invoice_ids) > 1: result['domain'] = "[('id','in',%s)]" % invoice_ids.ids elif len(invoice_ids) == 1: result['views'] = [(form_view_id, 'form')] result['res_id'] = invoice_ids.ids[0] else: result = {'type': 'ir.actions.act_window_close'} return result @api.depends('invoice_ids') def get_invoiced_count(self): for rec in self: rec.invoice_count = len(rec.invoice_ids) @api.model def membership_state(self): for cur_rec in self.search([]): if cur_rec.plan_state != 'draft' and cur_rec.end_date and not cur_rec.auto_renewal: if cur_rec.plan_state == 'in_progress' and str( datetime.datetime.now().date()) >= str( cur_rec.end_date): cur_rec.plan_state = 'expired' cur_rec.revokingpricelist() if not (self.env['membership.membership'].search( [('customer_name.name', '=', cur_rec.customer_name.name), ('plan_state', '=', 'in_progress')])): pending_membership = self.env[ 'membership.membership'].search([ ('customer_name.name', '=', cur_rec.customer_name.name), ('plan_state', '=', 'pending') ]) if pending_membership: pending_membership.plan_state = 'draft' pending_membership.confirm_membership() self.send_reminder_mail() @api.multi def membership_renew(self): for current_rec in self: if current_rec.plan_state in ['expired', 'close', 'cancel']: current_rec.create_membership() current_rec.plan_state = 'renewed' wizard_id = self.env['membership.message.wizard'].create( {'message': 'Membership Renewed.'}) return { 'name': ("Message"), 'view_mode': 'form', 'view_id': False, 'view_type': 'form', 'res_model': 'membership.message.wizard', 'res_id': wizard_id.id, 'type': 'ir.actions.act_window', 'nodestroy': True, 'target': 'new', } return False @api.multi def create_membership(self): date = datetime.datetime.today() if self.trial_period: if self.trial_plan_unit == 'day': date = date + relativedelta(days=self.trial_duration) if self.trial_plan_unit == 'month': date = date + relativedelta(months=self.trial_duration) if self.trial_plan_unit == 'year': date = date + relativedelta(years=self.trial_duration) if self.trial_plan_unit == 'hour': date = date + relativedelta(hours=self.trial_duration) res = self.copy() res.start_date = str(date) res.membership_id = self.id self.state = "renewed" return res def assigningpricelist(self): self.customer_name.property_product_pricelist = self.membership_plan_id.membership_pricelist.id def revokingpricelist(self): pricelist = self.env['product.pricelist'].search([ ('name', '=', 'Public Pricelist') ]) self.customer_name.property_product_pricelist = pricelist.id def update_membership(self): count = self.env['membership.membership'].search([ ('customer_name.name', '=', self.customer_name.name), ('plan_state', '=', 'in_progress') ]) if count: if (count.immediate): count.reset_to_close() count.plan_state = 'update' elif count.after: self.plan_state = 'pending' else: return True def send_reminder_mail(self): for cur_rec in self.search([]): if cur_rec.plan_state != 'draft' and cur_rec.end_date: advanced_date = (datetime.datetime.now() + datetime.timedelta(days=10)).date() if (advanced_date >= cur_rec.end_date and cur_rec.plan_state == 'in_progress'): cur_rec.reminder_mail_send() @api.multi def get_cancel_mem(self): for current_rec in self: if current_rec.plan_state == 'draft': current_rec.plan_state = 'cancel' return True @api.multi def pay_cancel_invoice(self): for current_rec in self: for invoice_id in current_rec.invoice_ids: if invoice_id.state == 'draft': res = invoice_id.action_cancel() elif invoice_id.state == 'open': journal_id = self.env["ir.default"].get( 'res.config.settings', 'journal_id') if not journal_id: raise UserError( _("Default Journal not found please the default journal in configuration under membership" )) journal = self.env['account.journal'].browse(journal_id) res = invoice_id.pay_and_reconcile(pay_journal=journal) return True @api.multi def reset_to_close(self): for current_rec in self: if current_rec.plan_state not in [ 'close', 'cancel', 'renewed', 'update' ]: if current_rec.invoice_ids: self.pay_cancel_invoice() current_rec.revokingpricelist() current_rec.plan_state = 'close' if self._context.get('close_refund'): return current_rec.action_view_invoice() return True
class academia_student(models.Model): _inherit = ['portal.mixin', 'mail.thread', 'mail.activity.mixin'] _name = "academia.student" _description = "Modelo para formulacion de estudiantes" @api.depends('calificaciones_id') def calcula_promedio(self): acum = 0.0 for xcal in self.calificaciones_id: acum+= xcal.calificacion if acum: promedio = acum/len(self.calificaciones_id) self.promedio = promedio @api.depends('invoice_ids') def calcula_amount(self): acum = 0.0 for xcal in self.invoice_ids: acum+= xcal.amount_total if acum: self.amount_invoice = acum @api.model def _get_school_default(self): school_id = self.env['res.partner'].search([('name','=','Escuela Comodin')]) return school_id name = fields.Char('Nombre', size = 128, required = True, track_visibility = 'onchange') last_name = fields.Char('Apellido', size = 128) photo = fields.Binary('Fotografia') create_date = fields.Datetime('Fecha de creacion', readonly=True) note = fields.Html('Comentarios') active = fields.Boolean('Activo', default=True) age = fields.Integer ('Edad', copy=False) curp = fields.Char('curp', size=18, copy=False) state = fields.Selection([ ('draft','Documento borrador'), ('process','Proceso'), ('done', 'Egresado'), ('cancel', 'Expulsado')],'Estado', default="draft") ##Relacionales partner_id = fields.Many2one('res.partner', 'Escuela', default=_get_school_default, copy=False) country = fields.Many2one('res.country', 'Pais', related='partner_id.country_id') calificaciones_id = fields.One2many( 'academia.calificacion', 'student_id', 'Calificaciones') invoice_ids = fields.Many2many('account.invoice', 'student_invoice_rel', 'student_id', 'invoice_id', 'Facturas') grado_id = fields.Many2one('academia.grado', 'Grado') promedio = fields.Float('Promedio', digits=(14,2), compute="calcula_promedio") amount_invoice = fields.Float('Monto Facturado', digits=(14,2), compute="calcula_amount", store=True) @api.onchange('grado_id') def onchange_grado(self): calificaciones_list = [] for materia in self.grado_id.materia_ids: xval = (0,0,{ 'name': materia.materia_id.id, 'calificacion': 5 }) calificaciones_list.append(xval) self.update({'calificaciones_id':calificaciones_list}) @api.one @api.constrains('curp') def _check_lines(self): if len(self.curp) < 18: raise exceptions.ValidationError("Curp debe ser de 18 caracteres.") @api.model def create(self,values): if values['name']: nombre = values['name'] exist_ids = self.env['academia.student'].search([('name', '=', self.name)]) if exist_ids: values.update({ 'name': values['name'] + "(copia)", }) res = super(academia_student, self).create(values) partner_obj = self.env['res.partner'] vals_to_partner = { 'name': res['name']+" " + res['last_name'], 'company_type': 'student_id', 'student_id': res['id'], } print (vals_to_partner) partner_id = partner_obj.create(vals_to_partner) print("===>partner_id", partner_id) return res _order = "name" _default = { 'active' : True, } @api.multi def done(self): self.state = 'done' return True @api.multi def confirm(self): self.state = 'process' return True @api.multi def cancel(self): self.state = 'cancel' return True @api.multi def draft(self): self.state = 'draft' return True
class MrpWorkcenterProductivity(models.Model): _name = "mrp.workcenter.productivity" _description = "Workcenter Productivity Log" _order = "id desc" _rec_name = "loss_id" _check_company_auto = True def _get_default_company_id(self): company_id = False if self.env.context.get('default_company_id'): company_id = self.env.context['default_company_id'] if not company_id and self.env.context.get('default_workorder_id'): workorder = self.env['mrp.workorder'].browse( self.env.context['default_workorder_id']) company_id = workorder.company_id if not company_id and self.env.context.get('default_workcenter_id'): workcenter = self.env['mrp.workcenter'].browse( self.env.context['default_workcenter_id']) company_id = workcenter.company_id if not company_id: company_id = self.env.company return company_id production_id = fields.Many2one('mrp.production', string='Manufacturing Order', related='workorder_id.production_id', readonly='True') workcenter_id = fields.Many2one('mrp.workcenter', "Work Center", required=True, check_company=True) company_id = fields.Many2one( 'res.company', required=True, index=True, default=lambda self: self._get_default_company_id()) workorder_id = fields.Many2one('mrp.workorder', 'Work Order', check_company=True) user_id = fields.Many2one('res.users', "User", default=lambda self: self.env.uid) loss_id = fields.Many2one('mrp.workcenter.productivity.loss', "Loss Reason", ondelete='restrict', required=True) loss_type = fields.Selection(string="Effectiveness", related='loss_id.loss_type', store=True, readonly=False) description = fields.Text('Description') date_start = fields.Datetime('Start Date', default=fields.Datetime.now, required=True) date_end = fields.Datetime('End Date') duration = fields.Float('Duration', compute='_compute_duration', store=True) @api.depends('date_end', 'date_start') def _compute_duration(self): for blocktime in self: if blocktime.date_start and blocktime.date_end: d1 = fields.Datetime.from_string(blocktime.date_start) d2 = fields.Datetime.from_string(blocktime.date_end) diff = d2 - d1 if (blocktime.loss_type not in ('productive', 'performance') ) and blocktime.workcenter_id.resource_calendar_id: r = blocktime.workcenter_id._get_work_days_data_batch( d1, d2)[blocktime.workcenter_id.id]['hours'] blocktime.duration = round(r * 60, 2) else: blocktime.duration = round(diff.total_seconds() / 60.0, 2) else: blocktime.duration = 0.0 def button_block(self): self.ensure_one() self.workcenter_id.order_ids.end_all()
class PurchaseRequisition(models.Model): _name = "purchase.requisition" _description = "Purchase Requisition" _inherit = ['mail.thread', 'mail.activity.mixin'] _order = "id desc" def _get_type_id(self): return self.env['purchase.requisition.type'].search([], limit=1) name = fields.Char(string='Agreement Reference', required=True, copy=False, default='New', readonly=True) origin = fields.Char(string='Source Document') order_count = fields.Integer(compute='_compute_orders_number', string='Number of Orders') vendor_id = fields.Many2one('res.partner', string="Vendor", domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") type_id = fields.Many2one('purchase.requisition.type', string="Agreement Type", required=True, default=_get_type_id) ordering_date = fields.Date(string="Ordering Date", tracking=True) date_end = fields.Datetime(string='Agreement Deadline', tracking=True) schedule_date = fields.Date(string='Delivery Date', index=True, help="The expected and scheduled delivery date where all the products are received", tracking=True) user_id = fields.Many2one('res.users', string='Purchase Representative', default= lambda self: self.env.user) description = fields.Text() company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company) purchase_ids = fields.One2many('purchase.order', 'requisition_id', string='Purchase Orders', states={'done': [('readonly', True)]}) line_ids = fields.One2many('purchase.requisition.line', 'requisition_id', string='Products to Purchase', states={'done': [('readonly', True)]}, copy=True) state = fields.Selection(PURCHASE_REQUISITION_STATES, 'Status', tracking=True, required=True, copy=False, default='draft') state_blanket_order = fields.Selection(PURCHASE_REQUISITION_STATES, compute='_set_state') is_quantity_copy = fields.Selection(related='type_id.quantity_copy', readonly=True) currency_id = fields.Many2one('res.currency', 'Currency', required=True, default=lambda self: self.env.company.currency_id.id) @api.depends('state') def _set_state(self): self.state_blanket_order = self.state @api.onchange('vendor_id') def _onchange_vendor(self): requisitions = self.env['purchase.requisition'].search([ ('vendor_id', '=', self.vendor_id.id), ('state', '=', 'ongoing'), ('type_id.quantity_copy', '=', 'none'), ]) if any(requisitions): title = _("Warning for %s") % self.vendor_id.name message = _("There is already an open blanket order for this supplier. We suggest you to use to complete this open blanket order instead of creating a new one.") warning = { 'title': title, 'message': message } return {'warning': warning} @api.depends('purchase_ids') def _compute_orders_number(self): for requisition in self: requisition.order_count = len(requisition.purchase_ids) def action_cancel(self): # try to set all associated quotations to cancel state for requisition in self: for requisition_line in requisition.line_ids: requisition_line.supplier_info_ids.unlink() requisition.purchase_ids.button_cancel() for po in requisition.purchase_ids: po.message_post(body=_('Cancelled by the agreement associated to this quotation.')) self.write({'state': 'cancel'}) def action_in_progress(self): self.ensure_one() if not all(obj.line_ids for obj in self): raise UserError(_("You cannot confirm agreement '%s' because there is no product line.") % self.name) if self.type_id.quantity_copy == 'none' and self.vendor_id: for requisition_line in self.line_ids: if requisition_line.price_unit <= 0.0: raise UserError(_('You cannot confirm the blanket order without price.')) if requisition_line.product_qty <= 0.0: raise UserError(_('You cannot confirm the blanket order without quantity.')) requisition_line.create_supplier_info() self.write({'state': 'ongoing'}) else: self.write({'state': 'in_progress'}) # Set the sequence number regarding the requisition type if self.name == 'New': if self.is_quantity_copy != 'none': self.name = self.env['ir.sequence'].next_by_code('purchase.requisition.purchase.tender') else: self.name = self.env['ir.sequence'].next_by_code('purchase.requisition.blanket.order') def action_open(self): self.write({'state': 'open'}) def action_draft(self): self.ensure_one() self.name = 'New' self.write({'state': 'draft'}) def action_done(self): """ Generate all purchase order based on selected lines, should only be called on one agreement at a time """ if any(purchase_order.state in ['draft', 'sent', 'to approve'] for purchase_order in self.mapped('purchase_ids')): raise UserError(_('You have to cancel or validate every RfQ before closing the purchase requisition.')) for requisition in self: for requisition_line in requisition.line_ids: requisition_line.supplier_info_ids.unlink() self.write({'state': 'done'}) def _prepare_tender_values(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values): return{ 'origin': origin, 'date_end': values['date_planned'], 'user_id': False, 'warehouse_id': values.get('warehouse_id') and values['warehouse_id'].id or False, 'company_id': company_id.id, 'line_ids': [(0, 0, { 'product_id': product_id.id, 'product_uom_id': product_uom.id, 'product_qty': product_qty, })], } def unlink(self): if any(requisition.state not in ('draft', 'cancel') for requisition in self): raise UserError(_('You can only delete draft requisitions.')) # Draft requisitions could have some requisition lines. self.mapped('line_ids').unlink() return super(PurchaseRequisition, self).unlink()
class OCD(models.Model): _name = 'calendar.ocd' _description = 'Create an OCD' principal = fields.Boolean(_('Principal Task')) name = fields.Char(_('Name'), required=True) acronym = fields.Char(_('Acronym'), required=True) job_ids = fields.Many2many('hr.job', string=_('Members'), required=True) start_datetime = fields.Datetime(_('Start DateTime'), required=True) stop_datetime = fields.Datetime(_('End Datetime'), required=True) location = fields.Char(_('Location')) ocd_flag = fields.Integer('Create ocd in event') # RECURRENCE FIELD rrule = fields.Char('Recurrent Rule', compute='_compute_rrule', inverse='_inverse_rrule', store=True) rrule_type = fields.Selection( [('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)'), ('yearly', 'Year(s)')], string='Recurrence', help="Let the event automatically repeat at that interval") recurrency = fields.Boolean('Recurrent', help="Recurrent Meeting") recurrent_id = fields.Integer('Recurrent ID') recurrent_id_date = fields.Datetime('Recurrent ID date') end_type = fields.Selection([('count', 'Number of repetitions'), ('end_date', 'End date')], string='Recurrence Termination', default='count') interval = fields.Integer(string='Repeat Every', default=1, help="Repeat every (Days/Week/Month/Year)") count = fields.Integer(string='Repeat', help="Repeat x times", default=1) mo = fields.Boolean('Mon') tu = fields.Boolean('Tue') we = fields.Boolean('Wed') th = fields.Boolean('Thu') fr = fields.Boolean('Fri') sa = fields.Boolean('Sat') su = fields.Boolean('Sun') month_by = fields.Selection([('date', 'Date of month'), ('day', 'Day of month')], string='Option', default='date', oldname='select1') day = fields.Integer('Date of month', default=1) week_list = fields.Selection([('MO', 'Monday'), ('TU', 'Tuesday'), ('WE', 'Wednesday'), ('TH', 'Thursday'), ('FR', 'Friday'), ('SA', 'Saturday'), ('SU', 'Sunday')], string='Weekday') byday = fields.Selection([('1', 'First'), ('2', 'Second'), ('3', 'Third'), ('4', 'Fourth'), ('5', 'Fifth'), ('-1', 'Last')], string='By day') final_date = fields.Date('Repeat Until') @api.depends('byday', 'recurrency', 'final_date', 'rrule_type', 'month_by', 'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'su', 'day', 'week_list') def _compute_rrule(self): """ Gets Recurrence rule string according to value type RECUR of iCalendar from the values given. :return dictionary of rrule value. """ for meeting in self: if meeting.recurrency: meeting.rrule = meeting._rrule_serialize() else: meeting.rrule = '' @api.multi def _inverse_rrule(self): for meeting in self: if meeting.rrule: data = self._rrule_default_values() data['recurrency'] = True data.update( self._rrule_parse(meeting.rrule, data, meeting.start)) meeting.update(data) @api.constrains('start_datetime', 'stop_datetime') def _check_closing_date(self): if self.start_datetime and self.stop_datetime and self.stop_datetime < self.start_datetime: raise ValidationError( _('Ending datetime cannot be set before starting datetime.')) def _rrule_default_values(self): return { 'byday': False, 'recurrency': False, 'final_date': False, 'rrule_type': False, 'month_by': False, 'interval': 0, 'count': False, 'end_type': False, 'mo': False, 'tu': False, 'we': False, 'th': False, 'fr': False, 'sa': False, 'su': False, 'day': False, 'week_list': False } def _rrule_parse(self, rule_str, data, date_start): day_list = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'] rrule_type = ['yearly', 'monthly', 'weekly', 'daily'] ddate = fields.Datetime.from_string(date_start) if 'Z' in rule_str and not ddate.tzinfo: ddate = ddate.replace(tzinfo=pytz.timezone('UTC')) rule = rrule.rrulestr(rule_str, dtstart=ddate) else: rule = rrule.rrulestr(rule_str, dtstart=ddate) if rule._freq > 0 and rule._freq < 4: data['rrule_type'] = rrule_type[rule._freq] data['count'] = rule._count data['interval'] = rule._interval data['final_date'] = rule._until and rule._until.strftime( DEFAULT_SERVER_DATETIME_FORMAT) #repeat weekly if rule._byweekday: for i in range(0, 7): if i in rule._byweekday: data[day_list[i]] = True data['rrule_type'] = 'weekly' #repeat monthly by nweekday ((weekday, weeknumber), ) if rule._bynweekday: data['week_list'] = day_list[list(rule._bynweekday)[0][0]].upper() data['byday'] = str(list(rule._bynweekday)[0][1]) data['month_by'] = 'day' data['rrule_type'] = 'monthly' if rule._bymonthday: data['day'] = list(rule._bymonthday)[0] data['month_by'] = 'date' data['rrule_type'] = 'monthly' #repeat yearly but for odoo it's monthly, take same information as monthly but interval is 12 times if rule._bymonth: data['interval'] = data['interval'] * 12 #FIXEME handle forever case #end of recurrence #in case of repeat for ever that we do not support right now if not (data.get('count') or data.get('final_date')): data['count'] = 100 if data.get('count'): data['end_type'] = 'count' else: data['end_type'] = 'end_date' return data @api.multi def _rrule_serialize(self): """ Compute rule string according to value type RECUR of iCalendar :return: string containing recurring rule (empty if no rule) """ if self.interval and self.interval < 0: raise UserError(_('interval cannot be negative.')) if self.count and self.count <= 0: raise UserError(_('Event recurrence interval cannot be negative.')) def get_week_string(freq): weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'] if freq == 'weekly': byday = [field.upper() for field in weekdays if self[field]] if byday: return ';BYDAY=' + ','.join(byday) return '' def set_events_to_ocd(self): partner_ocd_ids = self.env['hr.employee'].search([ ('job_id', 'in', self.job_ids.ids) ]).mapped('user_id.partner_id.id') lines1 = [(4, id, 0) for id in partner_ocd_ids] vals = { 'principal': self.principal, 'name': self.acronym, 'start': self.start_datetime, 'stop': self.stop_datetime, 'location': self.location, 'byday': self.byday, 'recurrency': self.recurrency, 'final_date': self.final_date, 'rrule_type': self.rrule_type, 'month_by': self.month_by, 'interval': self.interval, 'count': self.count, 'end_type': self.end_type, 'mo': self.mo, 'tu': self.tu, 'we': self.we, 'th': self.we, 'fr': self.fr, 'sa': self.sa, 'su': self.su, 'day': self.day, 'week_list': self.week_list, 'partner_ids': lines1, 'ocd': self.id, } self.env['calendar.event'].create(vals) self.ocd_flag = self.env['calendar.event'].search_count([('ocd', '=', self.id)]) @api.multi def write(self, vals): ocd_write = super(OCD, self).write(vals) ocd_event = self.env['calendar.event'].search([('ocd', '=', self.id)]) partner_ocd_ids = self.env['hr.employee'].search([ ('job_id', 'in', self.job_ids.ids) ]).mapped('user_id.partner_id.id') _logger.info(partner_ocd_ids) lines1 = [(6, 0, partner_ocd_ids)] val = { 'principal': self.principal, 'name': self.acronym, 'start': self.start_datetime, 'stop': self.stop_datetime, 'location': self.location, 'byday': self.byday, 'recurrency': self.recurrency, 'final_date': self.final_date, 'rrule_type': self.rrule_type, 'month_by': self.month_by, 'interval': self.interval, 'count': self.count, 'end_type': self.end_type, 'mo': self.mo, 'tu': self.tu, 'we': self.we, 'th': self.we, 'fr': self.fr, 'sa': self.sa, 'su': self.su, 'day': self.day, 'week_list': self.week_list, 'partner_ids': lines1, } ocd_event.write(val) return True @api.multi def unlink(self): ocd_event = self.env['calendar.event'].search([('ocd', '=', self.id)]) ocd_event.unlink() res = super(OCD, self).unlink() return True
class SaleEnquiry(models.Model): _name = "sale.enquiry" _description = "Sale Enquiry" @api.multi def get_manager_access(self): if self.env.user.has_group('sales_team.group_sale_manager'): self.is_manager = True @api.multi def get_all_approved(self): line_count = 0 approve_count = 0 for line in self.enquiry_line: line_count += 1 if line.approved: approve_count += 1 if line_count == approve_count: self.all_approved = True else: self.partially_approved = True if approve_count: self.state = 'partial' @api.depends('enquiry_line') def _get_order_ids(self): for enquiry in self: order_ids = enquiry.enquiry_line.mapped('sale_line_id').mapped( 'order_id') enquiry.update({ 'order_count': len(set(order_ids.ids)), 'order_ids': order_ids.ids }) name = fields.Char(string='Enquiry Reference', required=True, copy=False, readonly=True, states={'draft': [('readonly', False)]}, index=True, default=lambda self: _('New')) client_order_ref = fields.Char(string='Customer Reference', copy=False) state = fields.Selection([ ('draft', 'Enquiry'), ('sent', 'Sent For Approval'), ('waiting', 'Waiting'), ('partial', 'Partially Approved'), ('approved', 'Approved'), ('quotation', 'Quotation'), ('done', 'done'), ('cancel', 'Cancelled'), ], string='Status', readonly=True, copy=False, index=True, track_visibility='onchange', track_sequence=3, default='draft') date_enquiry = fields.Datetime(string='Enquiry Date', readonly=True, index=True, states={ 'draft': [('readonly', False)], 'sent': [('readonly', False)] }, copy=False, default=fields.Datetime.now) enquiry_line = fields.One2many('sale.enquiry.line', 'enquiry_id', string='Order Lines', states={ 'cancel': [('readonly', True)], 'done': [('readonly', True)] }, copy=True, auto_join=True) order_count = fields.Integer(string='Order Count', compute='_get_order_ids', readonly=True) order_ids = fields.Many2many("sale.order", string='Quotations', compute="_get_order_ids", readonly=True, copy=False) partner_id = fields.Many2one( 'res.partner', string='Customer', readonly=True, states={ 'draft': [('readonly', False)], 'sent': [('readonly', False)] }, required=True, change_default=True, index=True, track_visibility='always', track_sequence=1, help= "You can find a customer by its Name, TIN, Email or Internal Reference." ) all_approved = fields.Boolean('All approved', default=False, compute='get_all_approved') partially_approved = fields.Boolean('Partially Approved', default=False, compute='get_all_approved') is_manager = fields.Boolean('manager', compute='get_manager_access', default=False) @api.multi def action_create_quotation(self): sale_order = self.env['sale.order'].create({ 'partner_id': self.partner_id.id, 'enquiry_id': self.id, 'payment_type': 'lc' }) print(self._context) if sale_order: sale_line_obj = self.env['sale.order.line'] for line in self.enquiry_line: if line.state in ('available', 'approved'): so_line = sale_line_obj.create({ 'price_unit': line.price_unit, 'product_uom_qty': line.product_uom_qty, 'order_id': sale_order.id, 'discount': 0.0, 'product_id': line.product_id.id, }) line.sale_line_id = so_line.id self.state = 'quotation' if self._context.get('open_quotation', False): return self.action_view_order() return {'type': 'ir.actions.act_window_close'} @api.multi def action_view_order(self): order_ids = self.mapped('order_ids') action = { 'name': _('Quotation'), 'domain': [('id', '=', order_ids.ids)], 'view_type': 'form', 'res_model': 'sale.order', 'view_id': False, 'view_mode': 'tree,form', 'type': 'ir.actions.act_window', } return action @api.onchange('all_approved') def onchange_all_approved(self): if self.all_approved: self.state = 'approved' @api.multi def action_cancel(self): self.state = 'cancel' @api.multi def action_approve_all_prices(self): for line in self.enquiry_line: line.state = 'approved' line.approved = True self.all_approved = True self.state = 'approved' @api.model def create(self, vals): if vals.get('name', _('New')) == _('New'): vals['name'] = self.env['ir.sequence'].next_by_code( 'sale.enquiry') or _('New') enquiry_line = vals.get('enquiry_line') for line in enquiry_line: if line[2].get('product_id') and line[2].get('price_unit'): product_id = self.env['product.product'].browse( line[2].get('product_id')) if product_id: if line[2].get('price_unit') <= product_id.lst_price: line[2].update({ 'state': 'available', 'approved': True }) else: line[2].update({'state': 'draft'}) vals.update({'enquiry_line': enquiry_line}) result = super(SaleEnquiry, self).create(vals) return result @api.multi def action_send_for_clarification(self): self.state = 'sent' @api.multi def action_approve(self): self.state = 'approved'
class StockRequest(models.Model): _name = "stock.request" _description = "Stock Request" _inherit = 'stock.request.abstract' def _get_default_requested_by(self): return self.env['res.users'].browse(self.env.uid) name = fields.Char(states={'draft': [('readonly', False)]}) state = fields.Selection( selection=REQUEST_STATES, string='Status', copy=False, default='draft', index=True, readonly=True, track_visibility='onchange', ) requested_by = fields.Many2one( 'res.users', 'Requested by', required=True, track_visibility='onchange', default=lambda s: s._get_default_requested_by(), ) expected_date = fields.Datetime( 'Expected Date', default=fields.Datetime.now, index=True, required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Date when you expect to receive the goods.", ) picking_policy = fields.Selection( [('direct', 'Receive each product when available'), ('one', 'Receive all products at once')], string='Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)]}, default='direct', ) move_ids = fields.One2many( comodel_name='stock.move', compute='_compute_move_ids', string='Stock Moves', readonly=True, ) picking_ids = fields.One2many( 'stock.picking', compute='_compute_picking_ids', string='Pickings', readonly=True, ) qty_in_progress = fields.Float( 'Qty In Progress', digits=dp.get_precision('Product Unit of Measure'), readonly=True, compute='_compute_qty', store=True, help="Quantity in progress.", ) qty_done = fields.Float( 'Qty Done', digits=dp.get_precision('Product Unit of Measure'), readonly=True, compute='_compute_qty', store=True, help="Quantity completed", ) picking_count = fields.Integer( string='Delivery Orders', compute='_compute_picking_ids', readonly=True, ) allocation_ids = fields.One2many(comodel_name='stock.request.allocation', inverse_name='stock_request_id', string='Stock Request Allocation') order_id = fields.Many2one( 'stock.request.order', readonly=True, ) warehouse_id = fields.Many2one(states={'draft': [('readonly', False)]}, readonly=True) location_id = fields.Many2one(states={'draft': [('readonly', False)]}, readonly=True) product_id = fields.Many2one(states={'draft': [('readonly', False)]}, readonly=True) product_uom_id = fields.Many2one(states={'draft': [('readonly', False)]}, readonly=True) product_uom_qty = fields.Float(states={'draft': [('readonly', False)]}, readonly=True) procurement_group_id = fields.Many2one( states={'draft': [('readonly', False)]}, readonly=True) company_id = fields.Many2one(states={'draft': [('readonly', False)]}, readonly=True) route_id = fields.Many2one(states={'draft': [('readonly', False)]}, readonly=True) _sql_constraints = [ ('name_uniq', 'unique(name, company_id)', 'Stock Request name must be unique'), ] @api.depends('allocation_ids') def _compute_move_ids(self): for request in self.sudo(): request.move_ids = request.allocation_ids.mapped('stock_move_id') @api.depends('allocation_ids') def _compute_picking_ids(self): for request in self.sudo(): request.picking_count = 0 request.picking_ids = self.env['stock.picking'] request.picking_ids = request.move_ids.filtered( lambda m: m.state != 'cancel').mapped('picking_id') request.picking_count = len(request.picking_ids) @api.depends('allocation_ids', 'allocation_ids.stock_move_id.state', 'allocation_ids.stock_move_id.move_line_ids', 'allocation_ids.stock_move_id.move_line_ids.qty_done') def _compute_qty(self): for request in self.sudo(): done_qty = sum( request.allocation_ids.mapped('allocated_product_qty')) open_qty = sum(request.allocation_ids.mapped('open_product_qty')) request.qty_done = request.product_id.uom_id._compute_quantity( done_qty, request.product_uom_id) request.qty_in_progress = \ request.product_id.uom_id._compute_quantity( open_qty, request.product_uom_id) @api.constrains('order_id', 'requested_by') def check_order_requested_by(self): if self.order_id and self.order_id.requested_by != self.requested_by: raise ValidationError(_('Requested by must be equal to the order')) @api.constrains('order_id', 'warehouse_id') def check_order_warehouse_id(self): if self.order_id and self.order_id.warehouse_id != self.warehouse_id: raise ValidationError(_('Warehouse must be equal to the order')) @api.constrains('order_id', 'location_id') def check_order_location(self): if self.order_id and self.order_id.location_id != self.location_id: raise ValidationError(_('Location must be equal to the order')) @api.constrains('order_id', 'procurement_group_id') def check_order_procurement_group(self): if (self.order_id and self.order_id.procurement_group_id != self.procurement_group_id): raise ValidationError( _('Procurement group must be equal to the order')) @api.constrains('order_id', 'company_id') def check_order_company(self): if self.order_id and self.order_id.company_id != self.company_id: raise ValidationError(_('Company must be equal to the order')) @api.constrains('order_id', 'expected_date') def check_order_expected_date(self): if self.order_id and self.order_id.expected_date != self.expected_date: raise ValidationError( _('Expected date must be equal to the order')) @api.constrains('order_id', 'picking_policy') def check_order_picking_policy(self): if (self.order_id and self.order_id.picking_policy != self.picking_policy): raise ValidationError( _('The picking policy must be equal to the order')) @api.multi def _action_confirm(self): self._action_launch_procurement_rule() self.state = 'open' @api.multi def action_confirm(self): self._action_confirm() return True def action_draft(self): self.state = 'draft' return True def action_cancel(self): self.sudo().mapped('move_ids')._action_cancel() self.state = 'cancel' return True def action_done(self): self.state = 'done' if self.order_id: self.order_id.check_done() return True def check_done(self): precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') for request in self: allocated_qty = sum( request.allocation_ids.mapped('allocated_product_qty')) qty_done = request.product_id.uom_id._compute_quantity( allocated_qty, request.product_uom_id) if float_compare(qty_done, request.product_uom_qty, precision_digits=precision) >= 0: request.action_done() return True def _prepare_procurement_values(self, group_id=False): """ Prepare specific key for moves or other components that will be created from a procurement rule coming from a stock request. This method could be override in order to add other custom key that could be used in move/po creation. """ return { 'date_planned': self.expected_date, 'warehouse_id': self.warehouse_id, 'stock_request_allocation_ids': self.id, 'group_id': group_id or self.procurement_group_id.id or False, 'route_ids': self.route_id, 'stock_request_id': self.id, } @api.multi def _action_launch_procurement_rule(self): """ Launch procurement group run method with required/custom fields genrated by a stock request. procurement group will launch '_run_move', '_run_buy' or '_run_manufacture' depending on the stock request product rule. """ precision = self.env['decimal.precision'].precision_get( 'Product Unit of Measure') errors = [] for request in self: if (request.state != 'draft' or request.product_id.type not in ('consu', 'product')): continue qty = 0.0 for move in request.move_ids.filtered( lambda r: r.state != 'cancel'): qty += move.product_qty if float_compare( qty, request.product_qty, precision_digits=precision) >= 0: continue values = request._prepare_procurement_values( group_id=request.procurement_group_id) try: # We launch with sudo because potentially we could create # objects that the user is not authorized to create, such # as PO. self.env['procurement.group'].sudo().run( request.product_id, request.product_uom_qty, request.product_uom_id, request.location_id, request.name, request.name, values) except UserError as error: errors.append(error.name) if errors: raise UserError('\n'.join(errors)) return True @api.multi def action_view_transfer(self): action = self.env.ref('stock.action_picking_tree_all').read()[0] pickings = self.mapped('picking_ids') if len(pickings) > 1: action['domain'] = [('id', 'in', pickings.ids)] elif pickings: action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')] action['res_id'] = pickings.id return action @api.model def create(self, vals): upd_vals = vals.copy() if upd_vals.get('name', '/') == '/': upd_vals['name'] = self.env['ir.sequence'].next_by_code( 'stock.request') return super().create(upd_vals) @api.multi def unlink(self): if self.filtered(lambda r: r.state != 'draft'): raise UserError(_('Only requests on draft state can be unlinked')) return super(StockRequest, self).unlink()
class Task(models.Model): _name = "project.task" _description = "Task" _date_name = "date_start" _inherit = ['mail.thread'] _mail_post_access = 'read' _order = "priority desc, sequence, date_start, name, id" @api.model def default_get(self, field_list): """ Set 'date_assign' if user_id is set. """ result = super(Task, self).default_get(field_list) if 'user_id' in result: result['date_assign'] = fields.Datetime.now() return result def _get_default_partner(self): if 'default_project_id' in self.env.context: default_project_id = self.env['project.project'].browse(self.env.context['default_project_id']) return default_project_id.exists().partner_id def _get_default_stage_id(self): """ Gives default stage_id """ project_id = self.env.context.get('default_project_id') if not project_id: return False return self.stage_find(project_id, [('fold', '=', False)]) @api.model def _read_group_stage_ids(self, stages, domain, order): search_domain = [('id', 'in', stages.ids)] if 'default_project_id' in self.env.context: search_domain = ['|', ('project_ids', '=', self.env.context['default_project_id'])] + search_domain stage_ids = stages._search(search_domain, order=order, access_rights_uid=SUPERUSER_ID) return stages.browse(stage_ids) active = fields.Boolean(default=True) name = fields.Char(string='Task Title', track_visibility='always', required=True, index=True) description = fields.Html(string='Description') priority = fields.Selection([ ('0','Non Starred'), ('1','Starred') ], default='0', index=True, string="Starred") sequence = fields.Integer(string='Sequence', index=True, default=10, help="Gives the sequence order when displaying a list of tasks.") stage_id = fields.Many2one('project.task.type', string='Stage', track_visibility='onchange', index=True, default=_get_default_stage_id, group_expand='_read_group_stage_ids', domain="[('project_ids', '=', project_id)]", copy=False) tag_ids = fields.Many2many('project.tags', string='Tags', oldname='categ_ids') kanban_state = fields.Selection([ ('normal', 'Grey'), ('done', 'Green'), ('blocked', 'Red')], string='Kanban State', copy=False, default='normal', required=True, track_visibility='onchange', help="A task's kanban state indicates special situations affecting it:\n" " * Grey is the default situation\n" " * Red indicates something is preventing the progress of this task\n" " * Green indicates the task is ready to be pulled to the next stage") create_date = fields.Datetime(index=True) write_date = fields.Datetime(index=True) #not displayed in the view but it might be useful with base_action_rule module (and it needs to be defined first for that) date_start = fields.Datetime(string='Starting Date', default=fields.Datetime.now, index=True, copy=False) date_end = fields.Datetime(string='Ending Date', index=True, copy=False) date_assign = fields.Datetime(string='Assigning Date', index=True, copy=False, readonly=True) date_deadline = fields.Date(string='Deadline', index=True, copy=False) date_last_stage_update = fields.Datetime(string='Last Stage Update', default=fields.Datetime.now, index=True, copy=False, readonly=True) project_id = fields.Many2one('project.project', string='Project', default=lambda self: self.env.context.get('default_project_id'), index=True, track_visibility='onchange', change_default=True) notes = fields.Text(string='Notes') planned_hours = fields.Float(string='Initially Planned Hours', help='Estimated time to do the task, usually set by the project manager when the task is in draft state.') remaining_hours = fields.Float(string='Remaining Hours', digits=(16,2), help="Total remaining time, can be re-estimated periodically by the assignee of the task.") user_id = fields.Many2one('res.users', string='Assigned to', default=lambda self: self.env.uid, index=True, track_visibility='always') partner_id = fields.Many2one('res.partner', string='Customer', default=_get_default_partner) manager_id = fields.Many2one('res.users', string='Project Manager', related='project_id.user_id', readonly=True) company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env['res.company']._company_default_get()) color = fields.Integer(string='Color Index') user_email = fields.Char(related='user_id.email', string='User Email', readonly=True) attachment_ids = fields.One2many('ir.attachment', 'res_id', domain=lambda self: [('res_model', '=', self._name)], auto_join=True, string='Attachments') # In the domain of displayed_image_id, we couln't use attachment_ids because a one2many is represented as a list of commands so we used res_model & res_id displayed_image_id = fields.Many2one('ir.attachment', domain="[('res_model', '=', 'project.task'), ('res_id', '=', id), ('mimetype', 'ilike', 'image')]", string='Displayed Image') legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked Explanation', readonly=True) legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True) legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True) @api.onchange('project_id') def _onchange_project(self): default_partner_id = self.env.context.get('default_partner_id') default_partner = self.env['res.partner'].browse(default_partner_id) if default_partner_id else None if self.project_id: self.partner_id = self.project_id.partner_id or default_partner self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)]) else: self.partner_id = default_partner self.stage_id = False @api.onchange('user_id') def _onchange_user(self): if self.user_id: self.date_start = fields.Datetime.now() @api.multi def copy(self, default=None): if default is None: default = {} if not default.get('name'): default['name'] = _("%s (copy)") % self.name if 'remaining_hours' not in default: default['remaining_hours'] = self.planned_hours return super(Task, self).copy(default) @api.constrains('date_start', 'date_end') def _check_dates(self): if any(self.filtered(lambda task: task.date_start and task.date_end and task.date_start > task.date_end)): raise ValidationError(_('Error ! Task starting date must be lower than its ending date.')) # Override view according to the company definition @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): # read uom as admin to avoid access rights issues, e.g. for portal/share users, # this should be safe (no context passed to avoid side-effects) obj_tm = self.env.user.company_id.project_time_mode_id tm = obj_tm and obj_tm.name or 'Hours' res = super(Task, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) # read uom as admin to avoid access rights issues, e.g. for portal/share users, # this should be safe (no context passed to avoid side-effects) obj_tm = self.env.user.company_id.project_time_mode_id # using get_object to get translation value uom_hour = self.env.ref('product.product_uom_hour', False) if not obj_tm or not uom_hour or obj_tm.id == uom_hour.id: return res eview = etree.fromstring(res['arch']) # if the project_time_mode_id is not in hours (so in days), display it as a float field def _check_rec(eview): if eview.attrib.get('widget', '') == 'float_time': eview.set('widget', 'float') for child in eview: _check_rec(child) return True _check_rec(eview) res['arch'] = etree.tostring(eview) # replace reference of 'Hours' to 'Day(s)' for f in res['fields']: # TODO this NOT work in different language than english # the field 'Initially Planned Hours' should be replaced by 'Initially Planned Days' # but string 'Initially Planned Days' is not available in translation if 'Hours' in res['fields'][f]['string']: res['fields'][f]['string'] = res['fields'][f]['string'].replace('Hours', obj_tm.name) return res @api.model def get_empty_list_help(self, help): self = self.with_context( empty_list_help_id=self.env.context.get('default_project_id'), empty_list_help_model='project.project', empty_list_help_document_name=_("tasks") ) return super(Task, self).get_empty_list_help(help) # ---------------------------------------- # Case management # ---------------------------------------- def stage_find(self, section_id, domain=[], order='sequence'): """ Override of the base.stage method Parameter of the stage search taken from the lead: - section_id: if set, stages must belong to this section or be a default stage; if not set, stages must be default stages """ # collect all section_ids section_ids = [] if section_id: section_ids.append(section_id) section_ids.extend(self.mapped('project_id').ids) search_domain = [] if section_ids: search_domain = [('|')] * (len(section_ids) - 1) for section_id in section_ids: search_domain.append(('project_ids', '=', section_id)) search_domain += list(domain) # perform search, return the first found return self.env['project.task.type'].search(search_domain, order=order, limit=1).id # ------------------------------------------------ # CRUD overrides # ------------------------------------------------ @api.model def create(self, vals): # context: no_log, because subtype already handle this context = dict(self.env.context, mail_create_nolog=True) # for default stage if vals.get('project_id') and not context.get('default_project_id'): context['default_project_id'] = vals.get('project_id') # user_id change: update date_assign if vals.get('user_id'): vals['date_assign'] = fields.Datetime.now() task = super(Task, self.with_context(context)).create(vals) return task @api.multi def write(self, vals): now = fields.Datetime.now() # stage change: update date_last_stage_update if 'stage_id' in vals: vals['date_last_stage_update'] = now # reset kanban state when changing stage if 'kanban_state' not in vals: vals['kanban_state'] = 'normal' # user_id change: update date_assign if vals.get('user_id'): vals['date_assign'] = now result = super(Task, self).write(vals) return result # --------------------------------------------------- # Mail gateway # --------------------------------------------------- @api.multi def _track_template(self, tracking): res = super(Task, self)._track_template(tracking) test_task = self[0] changes, tracking_value_ids = tracking[test_task.id] if 'stage_id' in changes and test_task.stage_id.mail_template_id: res['stage_id'] = (test_task.stage_id.mail_template_id, {'composition_mode': 'mass_mail'}) return res @api.multi def _track_subtype(self, init_values): self.ensure_one() if 'kanban_state' in init_values and self.kanban_state == 'blocked': return 'project.mt_task_blocked' elif 'kanban_state' in init_values and self.kanban_state == 'done': return 'project.mt_task_ready' elif 'user_id' in init_values and self.user_id: # assigned -> new return 'project.mt_task_new' elif 'stage_id' in init_values and self.stage_id and self.stage_id.sequence <= 1: # start stage -> new return 'project.mt_task_new' elif 'stage_id' in init_values: return 'project.mt_task_stage' return super(Task, self)._track_subtype(init_values) @api.multi def _notification_recipients(self, message, groups): """ Handle project users and managers recipients that can convert assign tasks and create new one directly from notification emails. """ groups = super(Task, self)._notification_recipients(message, groups) self.ensure_one() if not self.user_id: take_action = self._notification_link_helper('assign') project_actions = [{'url': take_action, 'title': _('I take it')}] else: project_actions = [] new_group = ( 'group_project_user', lambda partner: bool(partner.user_ids) and any(user.has_group('project.group_project_user') for user in partner.user_ids), { 'actions': project_actions, }) return [new_group] + groups @api.model def message_get_reply_to(self, res_ids, default=None): """ Override to get the reply_to of the parent project. """ tasks = self.sudo().browse(res_ids) project_ids = tasks.mapped('project_id').ids aliases = self.env['project.project'].message_get_reply_to(project_ids, default=default) return {task.id: aliases.get(task.project_id.id, False) for task in tasks} @api.multi def email_split(self, msg): email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) # check left-part is not already an alias aliases = self.mapped('project_id.alias_name') return filter(lambda x: x.split('@')[0] not in aliases, email_list) @api.model def message_new(self, msg, custom_values=None): """ Override to updates the document according to the email. """ if custom_values is None: custom_values = {} defaults = { 'name': msg.get('subject'), 'planned_hours': 0.0, 'partner_id': msg.get('author_id') } defaults.update(custom_values) res = super(Task, self).message_new(msg, custom_values=defaults) task = self.browse(res) email_list = task.email_split(msg) partner_ids = filter(None, task._find_partner_from_emails(email_list, force_create=False)) task.message_subscribe(partner_ids) return res @api.multi def message_update(self, msg, update_vals=None): """ Override to update the task according to the email. """ if update_vals is None: update_vals = {} maps = { 'cost': 'planned_hours', } for line in msg['body'].split('\n'): line = line.strip() res = tools.command_re.match(line) if res: match = res.group(1).lower() field = maps.get(match) if field: try: update_vals[field] = float(res.group(2).lower()) except (ValueError, TypeError): pass email_list = self.email_split(msg) partner_ids = filter(None, self._find_partner_from_emails(email_list, force_create=False)) self.message_subscribe(partner_ids) return super(Task, self).message_update(msg, update_vals=update_vals) @api.multi def message_get_suggested_recipients(self): recipients = super(Task, self).message_get_suggested_recipients() for task in self.filtered('partner_id'): reason = _('Customer Email') if task.partner_id.email else _('Customer') task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason) return recipients @api.multi def message_get_email_values(self, notif_mail=None): res = super(Task, self).message_get_email_values(notif_mail=notif_mail) headers = {} if res.get('headers'): try: headers.update(safe_eval(res['headers'])) except Exception: pass if self.project_id: current_objects = filter(None, headers.get('X-Odoo-Objects', '').split(',')) current_objects.insert(0, 'project.project-%s, ' % self.project_id.id) headers['X-Odoo-Objects'] = ','.join(current_objects) if self.tag_ids: headers['X-Odoo-Tags'] = ','.join(self.tag_ids.mapped('name')) res['headers'] = repr(headers) return res
class ProductProduct(models.Model): """Book variant of product""" _inherit = "product.product" @api.model def default_get(self, fields): '''Overide method to get default category books''' res = super(ProductProduct, self).default_get(fields) category = self.env['product.category'].search([('name', '=', 'Books') ]) res.update({'categ_id': category.id}) return res @api.multi def name_get(self): ''' This method Returns the preferred display value (text representation) for the records with the given IDs. @param self : Object Pointer @param cr : Database Cursor @param uid : Current Logged in User @param ids :list of IDs @param context : context arguments, like language, time zone @return : tuples with the text representation of requested objects for to-many relationships ''' if not len(self.ids): return [] def _name_get(d): name = d.get('name', '') barcode = d.get('barcode', False) if barcode: name = '[%s] %s' % (barcode or '', name) return (d['id'], name) return map(_name_get, self.read(['name', 'barcode'])) @api.multi def _default_categ(self): ''' This method put default category of product @param self : Object Pointer @param cr : Database Cursor @param uid : Current Logged in User @param context : context arguments, like language, time zone ''' if self._context is None: self._context = {} if 'category_id' in self._context and self._context['category_id']: return self._context['category_id'] md = self.env['ir.model.data'] res = False try: res = md.get_object_reference('library', 'product_category_1')[1] except ValueError: res = False return res @api.multi def _tax_incl(self): ''' This method include tax in product @param self : Object Pointer @param cr : Database Cursor @param uid : Current Logged in User @param ids :list of IDs @param field_name : name of fields @param arg : other arguments @param context : context arguments, like language, time zone @return : Dictionary ''' res = {} for product in self: val = 0.0 for c in self.env['account.tax'].compute(product.taxes_id, product.list_price, 1, False): val += round(c['amount'], 2) res[product.id] = round(val + product.list_price, 2) return res @api.multi def _get_partner_code_name(self, product, parent_id): ''' This method get the partner code name @param self : Object Pointer @param cr : Database Cursor @param uid : Current Logged in User @param ids :list of IDs @param product : name of field @param partner_id : name of field @param context : context arguments, like language, time zone @return : Dictionary ''' for supinfo in product.seller_ids: if supinfo.name.id == parent_id: return { 'code': supinfo.product_code or product.default_code, 'name': supinfo.product_name or product.name } res = {'code': product.default_code, 'name': product.name} return res @api.multi def _product_code(self): ''' This method get the product code @param self : Object Pointer @param cr : Database Cursor @param uid : Current Logged in User @param ids :list of IDs @param name : name of field @param arg : other argument @param context : context arguments, like language, time zone @return : Dictionary ''' res = {} parent_id = self._context.get('parent_id', None) for p in self: res[p.id] = self._get_partner_code_name(p, parent_id)['code'] return res @api.multi def copy(self, default=None): ''' This method Duplicate record with given id updating it with default values @param self : Object Pointer @param cr : Database Cursor @param uid : Current Logged in User @param id : id of the record to copy @param default : dictionary of field values to override in the original values of the copied record @param context : standard Dictionary @return : id of the newly created record ''' if default is None: default = {} default.update({'author_ids': []}) return super(ProductProduct, self).copy(default) @api.model def create(self, vals): ''' This method is Create new student @param self : Object Pointer @param cr : Database Cursor @param uid : Current Logged in User @param vals : dictionary of new values to be set @param context : standard Dictionary @return :ID of newly created record. ''' def _uniq(seq): keys = {} for e in seq: keys[e] = 1 return keys.keys() # add link from editor to supplier: if 'editor' in vals: editor_id = vals['editor'] supplier_model = self.env['library.editor.supplier'] domain = [('name', '=', editor_id)] supplier_ids = [ idn.id for idn in supplier_model.search(domain) if idn.id > 0 ] suppliers = supplier_model.browse(supplier_ids) for obj in suppliers: supplier = [ 0, 0, { 'pricelist_ids': [], 'name': obj.supplier_id.id, 'sequence': obj.sequence, 'qty': 0, 'delay': 1, 'product_code': False, 'product_name': False } ] if 'seller_ids' not in vals: vals['seller_ids'] = [supplier] else: vals['seller_ids'].append(supplier) return super(ProductProduct, self).create(vals) @api.multi @api.depends('qty_available') def _compute_books_available(self): '''Computes the available books''' book_issue_obj = self.env['library.book.issue'] for rec in self: issue_ids = book_issue_obj.sudo().search([('name', '=', rec.id), ('state', 'in', ('issue', 'reissue'))]) occupied_no = 0.0 if issue_ids: occupied_no = len(issue_ids) # reduces the quantity when book is issued rec.books_available = rec.sudo().qty_available - occupied_no return True @api.multi @api.depends('books_available') def _compute_books_availablity(self): '''Method to compute availability of book''' for rec in self: if rec.books_available >= 1: rec.availability = 'available' else: rec.availability = 'notavailable' return True isbn = fields.Char('ISBN Code', unique=True, help="Shows International Standard Book Number") catalog_num = fields.Char('Catalog number', help="Shows Identification number of books") lang = fields.Many2one('product.lang', 'Language') editor_ids = fields.One2many('book.editor', "book_id", "Editor") author = fields.Many2one('library.author', 'Author') code = fields.Char(compute_="_product_code", method=True, string='Acronym', store=True) catalog_num = fields.Char('Catalog number', help="Reference number of book") creation_date = fields.Datetime( 'Creation date', readonly=True, help="Record creation date", default=lambda *a: time.strftime('%Y-%m-%d %H:%M:%S')) date_retour = fields.Datetime('Return Date', help='Book Return date') fine_lost = fields.Float('Fine Lost') fine_late_return = fields.Float('Late Return') tome = fields.Char('TOME', help="Stores information of work in several volume") nbpage = fields.Integer('Number of pages') rack = fields.Many2one('library.rack', 'Rack', help="Shows position of book") books_available = fields.Float("Books Available", compute="_compute_books_available") availability = fields.Selection([('available', 'Available'), ('notavailable', 'Not Available')], 'Book Availability', default='available', compute="_compute_books_availablity") link_ids = Many2manySym('product.product', 'book_book_rel', 'product_id1', 'product_id2', 'Related Books') back = fields.Selection([('hard', 'HardBack'), ('paper', 'PaperBack')], 'Binding Type', help="Shows books-binding type", default='paper') pocket = fields.Char('Pocket') num_pocket = fields.Char('Collection No.', help='Shows collection number in which' 'book resides') num_edition = fields.Integer('No. edition', help="Edition number of book") format = fields.Char('Format', help="The general physical appearance of a book") # price_cat = fields.Many2one('library.price.category', "Price category") is_ebook = fields.Boolean("Is EBook") is_subscription = fields.Boolean("Is Subscription based") subscrption_amt = fields.Float("Subscription Amount") attach_ebook = fields.Binary("Attach EBook") day_to_return_book = fields.Integer('Book Return Days') attchment_ids = fields.One2many('book.attachment', 'product_id', 'Book Attachments') _sql_constraints = [('unique_barcode', 'unique(barcode)', 'barcode field must be unique across\ all the products'), ('code_uniq', 'unique (code)', 'Code of the product must be unique !')] @api.onchange('is_ebook', 'attach_ebook') def onchange_availablilty(self): if self.is_ebook and self.attach_ebook: self.availability = 'available' @api.multi def action_purchase_order(self): purchase_line_obj = self.env['purchase.order.line'] purchase = purchase_line_obj.search([('product_id', '=', self.id)]) action = self.env.ref('purchase.purchase_form_action') result = action.read()[0] if not purchase: raise ValidationError(_('There is no Books Purchase !')) order = [] [order.append(order_rec.order_id.id) for order_rec in purchase] if len(order) != 1: result['domain'] = "[('id', 'in', " + str(order) + ")]" else: res = self.env.ref('purchase.purchase_order_form', False) result['views'] = [(res and res.id or False, 'form')] result['res_id'] = purchase.order_id.id return result @api.multi def action_book_req(self): '''Method to request book''' for rec in self: book_req = self.env['library.book.request'].search([('name', '=', rec.id)]) action = self.env.ref('library.action_lib_book_req') result = (action.read()[0]) if not book_req: raise ValidationError(_('There is no Book requested')) req = [] [req.append(request_rec.id) for request_rec in book_req] if len(req) != 1: result['domain'] = "[('id', 'in', " + str(req) + ")]" else: res = self.env.ref('library.view_book_library_req_form', False) result['views'] = [(res and res.id or False, 'form')] result['res_id'] = book_req.id return result
class IrSessions(models.Model): _name = 'ir.sessions' _description = "Sessions" user_id = fields.Many2one('res.users', 'User', ondelete='cascade', required=True) logged_in = fields.Boolean('Logged in', required=True, index=True) session_id = fields.Char('Session ID', size=100, required=True) session_seconds = fields.Integer('Session duration in seconds') multiple_sessions_block = fields.Boolean('Block Multiple Sessions') date_login = fields.Datetime('Login', required=True) date_logout = fields.Datetime('Logout') date_expiration = fields.Datetime('Expiration Date', required=True, index=True, default=lambda *a: fields.Datetime.now()) logout_type = fields.Selection(LOGOUT_TYPES, 'Logout Type') session_duration = fields.Char('Session Duration') user_kill_id = fields.Many2one( 'res.users', 'Killed by', ) unsuccessful_message = fields.Char('Unsuccessful', size=252) ip = fields.Char('Remote IP', size=15) ip_location = fields.Char('IP Location', ) remote_tz = fields.Char('Remote Time Zone', size=32, required=True) # Add other fields about the sessions from HEADER... _order = 'logged_in desc, date_expiration desc' # scheduler function to validate users session def validate_sessions(self): sessions = self.sudo().search([ ('date_expiration', '<=', fields.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)), ('logged_in', '=', True) ]) if sessions: sessions._close_session(logout_type='to') return True @api.multi def action_close_session(self): redirect = self._close_session(logout_type='sk') if redirect: return werkzeug.utils.redirect( '/web/login?db=%s' % self.env.cr.dbname, 303) @api.multi def _on_session_logout(self, logout_type=None): now = fields.datetime.now() cr = self.pool.cursor() # autocommit: our single update request will be performed atomically. # (In this way, there is no opportunity to have two transactions # interleaving their cr.execute()..cr.commit() calls and have one # of them rolled back due to a concurrent access.) cr.autocommit(True) for session in self: session_duration = str(now - datetime.strptime( session.date_login, DEFAULT_SERVER_DATETIME_FORMAT)).split( '.')[0] session.sudo().write({ 'logged_in': False, 'date_logout': now.strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'logout_type': logout_type, 'user_kill_id': SUPERUSER_ID, 'session_duration': session_duration, }) cr.commit() cr.close() return True @api.multi def _close_session(self, logout_type=None): redirect = False for r in self: if r.user_id.id == self.env.user.id: redirect = True session = root.session_store.get(r.session_id) session.logout(logout_type=logout_type, env=self.env) return redirect
class HrAttendance(models.Model): _name = "hr.attendance" _description = "Attendance" _order = "check_in desc" def _default_employee(self): return self.env.user.employee_id employee_id = fields.Many2one('hr.employee', string="Employee", default=_default_employee, required=True, ondelete='cascade', index=True) department_id = fields.Many2one('hr.department', string="Department", related="employee_id.department_id", readonly=True) check_in = fields.Datetime(string="Check In", default=fields.Datetime.now, required=True) check_out = fields.Datetime(string="Check Out") worked_hours = fields.Float(string='Worked Hours', compute='_compute_worked_hours', store=True, readonly=True) def name_get(self): result = [] for attendance in self: if not attendance.check_out: result.append( (attendance.id, _("%(empl_name)s from %(check_in)s") % { 'empl_name': attendance.employee_id.name, 'check_in': format_datetime( self.env, attendance.check_in, dt_format=False), })) else: result.append( (attendance.id, _("%(empl_name)s from %(check_in)s to %(check_out)s") % { 'empl_name': attendance.employee_id.name, 'check_in': format_datetime( self.env, attendance.check_in, dt_format=False), 'check_out': format_datetime( self.env, attendance.check_out, dt_format=False), })) return result @api.depends('check_in', 'check_out') def _compute_worked_hours(self): for attendance in self: if attendance.check_out and attendance.check_in: delta = attendance.check_out - attendance.check_in attendance.worked_hours = delta.total_seconds() / 3600.0 else: attendance.worked_hours = False @api.constrains('check_in', 'check_out') def _check_validity_check_in_check_out(self): """ verifies if check_in is earlier than check_out. """ for attendance in self: if attendance.check_in and attendance.check_out: if attendance.check_out < attendance.check_in: raise exceptions.ValidationError( _('"Check Out" time cannot be earlier than "Check In" time.' )) @api.constrains('check_in', 'check_out', 'employee_id') def _check_validity(self): """ Verifies the validity of the attendance record compared to the others from the same employee. For the same employee we must have : * maximum 1 "open" attendance record (without check_out) * no overlapping time slices with previous employee records """ for attendance in self: # we take the latest attendance before our check_in time and check it doesn't overlap with ours last_attendance_before_check_in = self.env['hr.attendance'].search( [ ('employee_id', '=', attendance.employee_id.id), ('check_in', '<=', attendance.check_in), ('id', '!=', attendance.id), ], order='check_in desc', limit=1) if last_attendance_before_check_in and last_attendance_before_check_in.check_out and last_attendance_before_check_in.check_out > attendance.check_in: raise exceptions.ValidationError( _("Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s" ) % { 'empl_name': attendance.employee_id.name, 'datetime': format_datetime( self.env, attendance.check_in, dt_format=False), }) if not attendance.check_out: # if our attendance is "open" (no check_out), we verify there is no other "open" attendance no_check_out_attendances = self.env['hr.attendance'].search( [ ('employee_id', '=', attendance.employee_id.id), ('check_out', '=', False), ('id', '!=', attendance.id), ], order='check_in desc', limit=1) if no_check_out_attendances: raise exceptions.ValidationError( _("Cannot create new attendance record for %(empl_name)s, the employee hasn't checked out since %(datetime)s" ) % { 'empl_name': attendance.employee_id.name, 'datetime': format_datetime(self.env, no_check_out_attendances.check_in, dt_format=False), }) else: # we verify that the latest attendance with check_in time before our check_out time # is the same as the one before our check_in time computed before, otherwise it overlaps last_attendance_before_check_out = self.env[ 'hr.attendance'].search([ ('employee_id', '=', attendance.employee_id.id), ('check_in', '<', attendance.check_out), ('id', '!=', attendance.id), ], order='check_in desc', limit=1) if last_attendance_before_check_out and last_attendance_before_check_in != last_attendance_before_check_out: raise exceptions.ValidationError( _("Cannot create new attendance record for %(empl_name)s, the employee was already checked in on %(datetime)s" ) % { 'empl_name': attendance.employee_id.name, 'datetime': format_datetime( self.env, last_attendance_before_check_out.check_in, dt_format=False), }) @api.model def _get_day_start_and_day(self, employee, dt): #Returns a tuple containing the datetime in naive UTC of the employee's start of the day # and the date it was for that employee if not dt.tzinfo: date_employee_tz = pytz.utc.localize(dt).astimezone( pytz.timezone(employee._get_tz())) else: date_employee_tz = dt start_day_employee_tz = date_employee_tz.replace(hour=0, minute=0, second=0) return (start_day_employee_tz.astimezone( pytz.utc).replace(tzinfo=None), start_day_employee_tz.date()) def _get_attendances_dates(self): # Returns a dictionnary {employee_id: set((datetimes, dates))} attendances_emp = defaultdict(set) for attendance in self.filtered(lambda a: a.employee_id.company_id. hr_attendance_overtime and a.check_in): check_in_day_start = attendance._get_day_start_and_day( attendance.employee_id, attendance.check_in) if check_in_day_start[0] < datetime.combine( attendance.employee_id.company_id.overtime_start_date, datetime.min.time()): continue attendances_emp[attendance.employee_id].add(check_in_day_start) if attendance.check_out: check_out_day_start = attendance._get_day_start_and_day( attendance.employee_id, attendance.check_out) attendances_emp[attendance.employee_id].add( check_out_day_start) return attendances_emp def _update_overtime(self, employee_attendance_dates=None): if employee_attendance_dates is None: employee_attendance_dates = self._get_attendances_dates() overtime_to_unlink = self.env['hr.attendance.overtime'] overtime_vals_list = [] for emp, attendance_dates in employee_attendance_dates.items(): # get_attendances_dates returns the date translated from the local timezone without tzinfo, # and contains all the date which we need to check for overtime emp_tz = pytz.timezone(emp._get_tz()) attendance_domain = [] for attendance_date in attendance_dates: attendance_domain = OR([ attendance_domain, [ ('check_in', '>=', attendance_date[0]), ('check_in', '<', attendance_date[0] + timedelta(hours=24)), ] ]) attendance_domain = AND([[('employee_id', '=', emp.id)], attendance_domain]) # Attendances per LOCAL day attendances_per_day = defaultdict( lambda: self.env['hr.attendance']) all_attendances = self.env['hr.attendance'].search( attendance_domain) for attendance in all_attendances: check_in_day_start = attendance._get_day_start_and_day( attendance.employee_id, attendance.check_in) attendances_per_day[check_in_day_start[1]] += attendance # As _attendance_intervals_batch and _leave_intervals_batch both take localized dates we need to localize those date start = pytz.utc.localize( min(attendance_dates, key=itemgetter(0))[0]) stop = pytz.utc.localize( max(attendance_dates, key=itemgetter(0))[0] + timedelta(hours=24)) # Retrieve expected attendance intervals expected_attendances = emp.resource_calendar_id._attendance_intervals_batch( start, stop, emp.resource_id)[emp.resource_id.id] # Substract Global Leaves expected_attendances -= emp.resource_calendar_id._leave_intervals_batch( start, stop, None)[False] # working_times = {date: [(start, stop)]} working_times = defaultdict(lambda: []) for expected_attendance in expected_attendances: # Exclude resource.calendar.attendance working_times[expected_attendance[0].date()].append( expected_attendance[:2]) overtimes = self.env['hr.attendance.overtime'].sudo().search([ ('employee_id', '=', emp.id), ('date', 'in', [day_data[1] for day_data in attendance_dates]), ('adjustment', '=', False), ]) company_threshold = emp.company_id.overtime_company_threshold / 60.0 employee_threshold = emp.company_id.overtime_employee_threshold / 60.0 for day_data in attendance_dates: attendance_date = day_data[1] attendances = attendances_per_day.get(attendance_date, self.browse()) unfinished_shifts = attendances.filtered( lambda a: not a.check_out) overtime_duration = 0 overtime_duration_real = 0 # Overtime is not counted if any shift is not closed or if there are no attendances for that day, # this could happen when deleting attendances. if not unfinished_shifts and attendances: # The employee usually doesn't work on that day if not working_times[attendance_date]: # User does not have any resource_calendar_attendance for that day (week-end for example) overtime_duration = sum( attendances.mapped('worked_hours')) overtime_duration_real = overtime_duration # The employee usually work on that day else: # Compute start and end time for that day planned_start_dt, planned_end_dt = False, False planned_work_duration = 0 for calendar_attendance in working_times[ attendance_date]: planned_start_dt = min( planned_start_dt, calendar_attendance[0] ) if planned_start_dt else calendar_attendance[0] planned_end_dt = max( planned_end_dt, calendar_attendance[1] ) if planned_end_dt else calendar_attendance[1] planned_work_duration += ( calendar_attendance[1] - calendar_attendance[0] ).total_seconds() / 3600.0 # Count time before, during and after 'working hours' pre_work_time, work_duration, post_work_time = 0, 0, 0 for attendance in attendances: # consider check_in as planned_start_dt if within threshold # if delta_in < 0: Checked in after supposed start of the day # if delta_in > 0: Checked in before supposed start of the day local_check_in = emp_tz.localize( attendance.check_in) delta_in = (planned_start_dt - local_check_in ).total_seconds() / 3600.0 # Started before or after planned date within the threshold interval if (delta_in > 0 and delta_in <= company_threshold) or\ (delta_in < 0 and abs(delta_in) <= employee_threshold): local_check_in = planned_start_dt local_check_out = emp_tz.localize( attendance.check_out) # same for check_out as planned_end_dt delta_out = (local_check_out - planned_end_dt ).total_seconds() / 3600.0 # if delta_out < 0: Checked out before supposed start of the day # if delta_out > 0: Checked out after supposed start of the day # Finised before or after planned date within the threshold interval if (delta_out > 0 and delta_out <= company_threshold) or\ (delta_out < 0 and abs(delta_out) <= employee_threshold): local_check_out = planned_end_dt # There is an overtime at the start of the day if local_check_in < planned_start_dt: pre_work_time += ( min(planned_start_dt, local_check_out) - local_check_in).total_seconds() / 3600.0 # Interval inside the working hours -> Considered as working time if local_check_in <= planned_end_dt and local_check_out >= planned_start_dt: work_duration += ( min(planned_end_dt, local_check_out) - max(planned_start_dt, local_check_in) ).total_seconds() / 3600.0 # There is an overtime at the end of the day if local_check_out > planned_end_dt: post_work_time += (local_check_out - max( planned_end_dt, local_check_in)).total_seconds() / 3600.0 # Overtime within the planned work hours + overtime before/after work hours is > company threshold overtime_duration = work_duration - planned_work_duration if pre_work_time > company_threshold: overtime_duration += pre_work_time if post_work_time > company_threshold: overtime_duration += post_work_time # Global overtime including the thresholds overtime_duration_real = sum( attendances.mapped( 'worked_hours')) - planned_work_duration overtime = overtimes.filtered( lambda o: o.date == attendance_date) if not float_is_zero(overtime_duration, 2) or unfinished_shifts: # Do not create if any attendance doesn't have a check_out, update if exists if unfinished_shifts: overtime_duration = 0 if not overtime and overtime_duration: overtime_vals_list.append({ 'employee_id': emp.id, 'date': attendance_date, 'duration': overtime_duration, 'duration_real': overtime_duration_real, }) elif overtime: overtime.sudo().write({ 'duration': overtime_duration, 'duration_real': overtime_duration }) elif overtime: overtime_to_unlink |= overtime self.env['hr.attendance.overtime'].sudo().create(overtime_vals_list) overtime_to_unlink.sudo().unlink() @api.model_create_multi def create(self, vals_list): res = super().create(vals_list) res._update_overtime() return res def write(self, vals): attendances_dates = self._get_attendances_dates() super(HrAttendance, self).write(vals) if any(field in vals for field in ['employee_id', 'check_in', 'check_out']): # Merge attendance dates before and after write to recompute the # overtime if the attendances have been moved to another day for emp, dates in self._get_attendances_dates().items(): attendances_dates[emp] |= dates self._update_overtime(attendances_dates) def unlink(self): attendances_dates = self._get_attendances_dates() super(HrAttendance, self).unlink() self._update_overtime(attendances_dates) @api.returns('self', lambda value: value.id) def copy(self): raise exceptions.UserError(_('You cannot duplicate an attendance.'))
class AcruxChatMessages(models.Model): _inherit = 'acrux.chat.base.message' _name = 'acrux.chat.message' _description = 'Chat Message' _order = 'date_message desc, id desc' name = fields.Char('name', compute='_compute_name', store=True) msgid = fields.Char('Message Id') contact_id = fields.Many2one('acrux.chat.conversation', 'Contact', required=True, ondelete='cascade') connector_id = fields.Many2one('acrux.chat.connector', related='contact_id.connector_id', string='Connector', store=True, readonly=True) date_message = fields.Datetime('Date', required=True, default=fields.Datetime.now) from_me = fields.Boolean('Message From Me') company_id = fields.Many2one('res.company', related='contact_id.company_id', string='Company', store=True, readonly=True) ttype = fields.Selection(selection_add=[('contact', 'Contact'), ('product', 'Product')], ondelete={'contact': 'cascade', 'product': 'cascade'}) error_msg = fields.Char('Error Message', readonly=True) event = fields.Selection([('unanswered', 'Unanswered Message'), ('new_conv', 'New Conversation'), ('res_conv', 'Resume Conversation')], string='Event') user_id = fields.Many2one('res.users', string='Sellman', compute='_compute_user_id', store=True) @api.depends('contact_id') def _compute_user_id(self): for r in self: user_id = r._get_user_id() r.user_id = user_id or self.env.user.id def _get_user_id(self): user_id = False if self.contact_id.sellman_id: user_id = self.contact_id.sellman_id.id return user_id @api.depends('text') def _compute_name(self): for r in self: if r.text: r.name = r.text[:10] else: r.name = '/' def conversation_update_time(self): for mess in self: is_info = bool(mess.ttype and mess.ttype.startswith('info')) if not is_info: data = {} cont = mess.contact_id if mess.from_me: data.update({'last_sent': mess.date_message}) if cont.last_received: data.update({'last_received_first': False}) else: # nº message data.update({'last_received': mess.date_message}) # 1º message if not cont.last_received_first: data.update({'last_received_first': mess.date_message}) if data: cont.write(data) @api.model def create(self, vals): if vals.get('contact_id'): Conv = self.env['acrux.chat.conversation'] conv_id = Conv.browse([vals.get('contact_id')]) if not conv_id.last_received: vals.update(event='new_conv') elif conv_id.last_received < date_timedelta(minutes=-12 * 60): ''' After 12 hours it is resume ''' vals.update(event='res_conv') ret = super(AcruxChatMessages, self).create(vals) ret.conversation_update_time() return ret @api.model def clean_number(self, number): return number.replace('+', '').replace(' ', '') @api.model def unlink_attachment(self, attach_to_del_ids, only_old=True): data = [('id', 'in', attach_to_del_ids)] if only_old: data.append(('delete_old', '=', True)) to_del = self.env['ir.attachment'].sudo().search(data) erased_ids = to_del.ids to_del.unlink() return erased_ids def unlink(self): ''' Delete attachment too ''' mess_ids = self.filtered(lambda x: x.res_model == 'ir.attachment' and x.res_id) attach_to_del = mess_ids.mapped('res_id') ret = super(AcruxChatMessages, self).unlink() if attach_to_del: self.unlink_attachment(attach_to_del) return ret def getJsDitc(self): out = self.read(['id', 'text', 'ttype', 'date_message', 'from_me', 'res_model', 'res_id', 'error_msg']) for x in out: x['date_message'] = date2local(self, x['date_message']) return out @api.model def get_url_image(self, res_model, res_id, field='image_chat', prod_id=None): url = False if not prod_id: prod_id = self.env[res_model].search([('id', '=', res_id)], limit=1) prod_id = prod_id if len(prod_id) == 1 else False if prod_id: field_obj = getattr(prod_id, field) if not field_obj: return prod_id, False check_weight = self.message_check_weight(field=field_obj) if check_weight: hash_id = hashlib.sha1(str((prod_id.write_date or prod_id.create_date or '')).encode('utf-8')).hexdigest()[0:7] url = '/web/static/chatresource/%s/%s_%s/%s' % (prod_id._name, prod_id.id, hash_id, field) base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') url = base_url.rstrip('/') + url return prod_id, url @api.model def get_url_attach(self, att_id): url = False attach_id = self.env['ir.attachment'].sudo().search([('id', '=', att_id)], limit=1) attach_id = attach_id if len(attach_id) == 1 else False if attach_id: self.message_check_weight(value=attach_id.file_size, raise_on=True) access_token = attach_id.generate_access_token()[0] url = '/web/chatresource/%s/%s' % (attach_id.id, access_token) base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') url = base_url.rstrip('/') + url return attach_id, url def message_parse(self): ''' Return message formated ''' self.ensure_one() message = False if self.ttype == 'text': message = self.ca_ttype_text() elif self.ttype in ['image', 'video', 'file']: message = self.ca_ttype_file() elif self.ttype == 'audio': message = self.ca_ttype_audio() elif self.ttype == 'product': message = self.ca_ttype_product() elif self.ttype == 'sale_order': message = self.ca_ttype_sale() elif self.ttype == 'location': message = self.ca_ttype_location() elif self.ttype == 'contact': raise ValidationError('Not implemented') message.update({ 'to': self.clean_number(self.contact_id.number), 'id': str(self.id), }) return message def message_send(self): '''Return msgid In: {'type': string (required) ['text', 'image', 'video', 'file', 'audio', 'location'], 'text': string (required), 'from': string, 'to': string, 'filename': string, 'url': string, 'address': string, 'latitude': string, 'longitude': string, } Out: {'msg_id': [string, False], } ''' self.ensure_one() ret = False connector_id = self.contact_id.connector_id if not self.ttype.startswith('info'): self.message_check_allow_send() data = self.message_parse() or {} result = connector_id.ca_request('send', data) msg_id = result.get('msg_id', False) if msg_id: self.msgid = msg_id return msg_id else: raise ValidationError('Server error.') else: return ret def message_check_allow_send(self): ''' Check elapsed time ''' self.ensure_one() if self.text and len(self.text) >= 4000: raise ValidationError(_('Message is to large (4.000 caracters).')) connector_id = self.contact_id.connector_id if connector_id.connector_type == 'gupshup': last_received = self.contact_id.last_received max_hours = connector_id.time_to_respond if max_hours and max_hours > 0: if not last_received: raise ValidationError(_('The client must have started a conversation.')) diff_hours = date_delta_seconds(last_received) / 3600 if diff_hours >= max_hours: raise ValidationError(_('The time to respond exceeded (%s hours). ' 'The limit is %s hours.') % (int(round(diff_hours)), max_hours)) def message_check_weight(self, field=None, value=None, raise_on=False): ''' Check size ''' self.ensure_one() ret = True limit = int(self.env['ir.config_parameter'].sudo().get_param('acrux_max_weight_kb') or '0') if limit > 0: limit *= 1024 # el parametro esta en kb pero el value pasa en bytes if field: value = len(base64.b64decode(field) if field else b'') if (value or 0) >= limit: if raise_on: msg = '%s Kb' % limit if limit < 1000 else '%s Mb' % (limit / 1000) raise ValidationError(_('Attachment exceeds the maximum size allowed (%s).') % msg) return False return ret @api.model def ca_ttype_text(self): ret = { 'type': 'text', 'text': self.text } return ret @api.model def ca_ttype_audio(self): if not self.res_id or self.res_model != 'ir.attachment': raise ValidationError('Attachment type is required.') attach_id, url = self.get_url_attach(self.res_id) if not attach_id: raise ValidationError('Attachment is required.') if not url: raise ValidationError('URL Attachment is required.') ret = { 'type': 'audio', 'url': url } return ret @api.model def ca_ttype_file(self): if not self.res_id or self.res_model != 'ir.attachment': raise ValidationError('Attachment type is required.') attach_id, url = self.get_url_attach(self.res_id) if not attach_id: raise ValidationError('Attachment is required.') if not url: raise ValidationError('URL Attachment is required.') ret = { 'type': self.ttype, 'text': self.text or '', 'filename': attach_id.name, 'url': url } return ret @api.model def ca_ttype_product(self): url = False filename = '' image_field = 'image_chat' # to set dynamic: self.res_filed if not self.res_id or self.res_model != 'product.product': raise ValidationError('Product type is required.') prod_id = self.env[self.res_model].browse(self.res_id) if not prod_id: raise ValidationError('Product is required.') # caption ---------- # or prod_id.name_get()[0][1] list_price = formatLang(self.env, prod_id.list_price, currency_obj=self.env.user.company_id.currency_id) caption = '%s\n%s / %s' % (self.text or prod_id.name.strip(), list_price, prod_id.uom_id.name) # image ---------- field_image = getattr(prod_id, image_field) if field_image: filename = secure_filename(prod_id.name) attach = get_binary_attach(self.env, self.res_model, self.res_id, image_field, fields_ret=['mimetype']) mimetype = attach and attach['mimetype'] if mimetype: ext = mimetype.split('/') if len(ext) == 2: filename = secure_filename('%s.%s' % (prod_id.name, ext[1])) prod_id, url = self.get_url_image(res_model=self.res_model, res_id=self.res_id, field=image_field, prod_id=prod_id) # send ---------- if not url: # Simple text message ret = { 'type': 'text', 'text': caption } return ret else: ret = { 'type': 'file', 'text': caption, 'filename': filename, 'url': url } return ret @api.model def ca_ttype_sale(self): if self.res_model != 'sale.order': raise ValidationError('Order type is required.') return self.ca_ttype_file() @api.model def ca_ttype_location(self): ''' Text format: name address latitude, longitude ''' parse = self.text.split('\n') if len(parse) != 3: return self.ca_ttype_text() cords = parse[2].split(',') ret = { 'type': 'location', 'address': '%s\n%s' % (parse[0].strip(), parse[1].strip()), 'latitude': cords[0].strip('( '), 'longitude': cords[1].strip(') '), } return ret
class HelpdeskTicket(models.Model): _inherit = 'helpdesk.ticket' date_logged = fields.Datetime(string='Date logged')
class Employee(models.Model): _name = "hr.employee" _description = "Employee" _order = 'name_related' _inherits = {'resource.resource': "resource_id"} _inherit = ['mail.thread'] _mail_post_access = 'read' @api.model def _default_image(self): image_path = get_module_resource('hr', 'static/src/img', 'default_image.png') return tools.image_resize_image_big( open(image_path, 'rb').read().encode('base64')) # we need a related field in order to be able to sort the employee by name name_related = fields.Char(related='resource_id.name', string="Resource Name", readonly=True, store=True) country_id = fields.Many2one('res.country', string='Nationality (Country)') birthday = fields.Date('Date of Birth') ssnid = fields.Char('SSN No', help='Social Security Number') sinid = fields.Char('SIN No', help='Social Insurance Number') identification_id = fields.Char(string='Identification No') gender = fields.Selection([('male', 'Male'), ('female', 'Female'), ('other', 'Other')]) marital = fields.Selection([('single', 'Single'), ('married', 'Married'), ('widower', 'Widower'), ('divorced', 'Divorced')], string='Marital Status') department_id = fields.Many2one('hr.department', string='Department') address_id = fields.Many2one('res.partner', string='Working Address') address_home_id = fields.Many2one('res.partner', string='Home Address') bank_account_id = fields.Many2one( 'res.partner.bank', string='Bank Account Number', domain="[('partner_id', '=', address_home_id)]", help='Employee bank salary account') work_phone = fields.Char('Work Phone') mobile_phone = fields.Char('Work Mobile') work_email = fields.Char('Work Email') work_location = fields.Char('Work Location') notes = fields.Text('Notes') parent_id = fields.Many2one('hr.employee', string='Manager') category_ids = fields.Many2many( 'hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', ) child_ids = fields.One2many('hr.employee', 'parent_id', string='Subordinates') resource_id = fields.Many2one('resource.resource', string='Resource', ondelete='cascade', required=True, auto_join=True) coach_id = fields.Many2one('hr.employee', string='Coach') job_id = fields.Many2one('hr.job', string='Job Title') passport_id = fields.Char('Passport No') color = fields.Integer('Color Index', default=0) city = fields.Char(related='address_id.city') login = fields.Char(related='user_id.login', readonly=True) last_login = fields.Datetime(related='user_id.login_date', string='Latest Connection', readonly=True) # image: all image fields are base64 encoded and PIL-supported image = fields.Binary( "Photo", default=_default_image, attachment=True, help= "This field holds the image used as photo for the employee, limited to 1024x1024px." ) image_medium = fields.Binary( "Medium-sized photo", attachment=True, help="Medium-sized photo of the employee. It is automatically " "resized as a 128x128px image, with aspect ratio preserved. " "Use this field in form views or some kanban views.") image_small = fields.Binary( "Small-sized photo", attachment=True, help="Small-sized photo of the employee. It is automatically " "resized as a 64x64px image, with aspect ratio preserved. " "Use this field anywhere a small image is required.") @api.constrains('parent_id') def _check_parent_id(self): for employee in self: if not employee._check_recursion(): raise ValidationError( _('Error! You cannot create recursive hierarchy of Employee(s).' )) @api.onchange('address_id') def _onchange_address(self): self.work_phone = self.address_id.phone self.mobile_phone = self.address_id.mobile @api.onchange('company_id') def _onchange_company(self): address = self.company_id.partner_id.address_get(['default']) self.address_id = address['default'] if address else False @api.onchange('department_id') def _onchange_department(self): self.parent_id = self.department_id.manager_id @api.onchange('user_id') def _onchange_user(self): self.work_email = self.user_id.email self.name = self.user_id.name self.image = self.user_id.image @api.model def create(self, vals): tools.image_resize_images(vals) return super(Employee, self).create(vals) @api.multi def write(self, vals): if 'address_home_id' in vals: account_id = vals.get('bank_account_id') or self.bank_account_id.id if account_id: self.env['res.partner.bank'].browse( account_id).partner_id = vals['address_home_id'] tools.image_resize_images(vals) return super(Employee, self).write(vals) @api.multi def unlink(self): resources = self.mapped('resource_id') super(Employee, self).unlink() return resources.unlink() @api.multi def action_follow(self): """ Wrapper because message_subscribe_users take a user_ids=None that receive the context without the wrapper. """ return self.message_subscribe_users() @api.multi def action_unfollow(self): """ Wrapper because message_unsubscribe_users take a user_ids=None that receive the context without the wrapper. """ return self.message_unsubscribe_users() @api.model def _message_get_auto_subscribe_fields(self, updated_fields, auto_follow_fields=None): """ Overwrite of the original method to always follow user_id field, even when not track_visibility so that a user will follow it's employee """ if auto_follow_fields is None: auto_follow_fields = ['user_id'] user_field_lst = [] for name, field in self._fields.items(): if name in auto_follow_fields and name in updated_fields and field.comodel_name == 'res.users': user_field_lst.append(name) return user_field_lst @api.multi def _message_auto_subscribe_notify(self, partner_ids): # Do not notify user it has been marked as follower of its employee. return
class SyncLink(models.Model): _name = "sync.link" _description = "Resource Links" _order = "id desc" relation = fields.Char("Relation Name", required=True) system1 = fields.Char("System 1", required=True) # index system2 only to make search "Odoo links" system2 = fields.Char("System 2", required=True, index=True) ref1 = fields.Char("Ref 1", required=True) ref2 = fields.Char("Ref 2", required=True) date = fields.Datetime(string="Sync Date", default=fields.Datetime.now, required=True) model = fields.Char("Odoo Model", index=True) def _auto_init(self): res = super(SyncLink, self)._auto_init() tools.create_unique_index( self._cr, "sync_link_refs_uniq_index", self._table, ["relation", "system1", "system2", "ref1", "ref2"], ) return res @api.model def _log(self, *args, **kwargs): log = self.env.context.get("log_function") if not log: return kwargs.setdefault("name", "sync.link") kwargs.setdefault("level", LOG_DEBUG) return log(*args, **kwargs) # External links @api.model def refs2vals(self, external_refs): external_refs = sorted(external_refs.items(), key=lambda code_value: code_value[0]) system1, ref1 = external_refs[0] system2, ref2 = external_refs[1] vals = { "system1": system1, "system2": system2, "ref1": ref1, "ref2": ref2, } for k in ["ref1", "ref2"]: if vals[k] is None: continue if isinstance(vals[k], list): vals[k] = [str(i) for i in vals[k]] else: vals[k] = str(vals[k]) return vals @api.model def _set_link_external(self, relation, external_refs, sync_date=None, allow_many2many=False, model=None): vals = self.refs2vals(external_refs) # Check for existing records if allow_many2many: existing = self._search_links_external(relation, external_refs) else: # check existing links for a part of external_refs refs1 = external_refs.copy() refs2 = external_refs.copy() for i, k in enumerate(external_refs.keys()): if i: refs1[k] = None else: refs2[k] = None existing = self._search_links_external( relation, refs1) or self._search_links_external( relation, refs2) if existing and not (existing.ref1 == vals["ref1"] and existing.ref2 == vals["ref2"]): raise ValidationError( _("%s link already exists: %s=%s, %s=%s") % ( relation, existing.system1, existing.ref1, existing.system1, existing.ref2, )) if existing: self._log("{} Use existing link: {}".format(relation, vals)) existing.update_links(sync_date) return existing if sync_date: vals["date"] = sync_date vals["relation"] = relation if model: vals["model"] = model self._log("Create link: %s" % vals) return self.create(vals) @api.model def _get_link_external(self, relation, external_refs): links = self._search_links_external(relation, external_refs) if len(links) > 1: raise ValidationError( _("get_link found multiple links. Use search_links for many2many relations" )) self._log("Get link: {} {} -> {}".format(relation, external_refs, links)) return links @api.model def _search_links_external(self, relation, external_refs, model=None, make_logs=False): vals = self.refs2vals(external_refs) domain = [("relation", "=", relation)] if model: domain.append(("model", "=", model)) for k, v in vals.items(): if not v: continue operator = "in" if isinstance(v, list) else "=" domain.append((k, operator, v)) links = self.search(domain) if make_logs: self._log("Search links: {} -> {}".format(domain, links)) return links def get(self, system): res = [] for r in self: if r.system1 == system: res.append(r.ref1) elif r.system2 == system: res.append(r.ref2) else: raise ValueError( _("Cannot find value for %s. Found: %s and %s") % (system, r.system1, r.system2)) return res # Odoo links @property def odoo(self): res = None for r in self: record = self.env[r.model].browse(int(getattr(r, ODOO_REF))) if res: res |= record else: res = record return res @property def external(self): res = [getattr(r, EXTERNAL_REF) for r in self] if len(res) == 1: return res[0] return res def _set_link_odoo(self, record, relation, ref, sync_date=None, allow_many2many=False): refs = {ODOO: record.id, EXTERNAL: ref} return self._set_link_external(relation, refs, sync_date, allow_many2many, record._name) def _get_link_odoo(self, relation, ref): refs = {ODOO: None, EXTERNAL: ref} return self._get_link_external(relation, refs) def _search_links_odoo(self, records, relation, refs=None): refs = {ODOO: records.ids, EXTERNAL: refs} return self._search_links_external(relation, refs, model=records._name, make_logs=True) # Common API def _get_link(self, rel, ref_info): if isinstance(ref_info, dict): # External link external_refs = ref_info return self._get_link_external(rel, external_refs) else: # Odoo link ref = ref_info return self._get_link_odoo(rel, ref) @property def sync_date(self): return min(r.date for r in self) def update_links(self, sync_date=None): if not sync_date: sync_date = fields.Datetime.now() self.write({"date": sync_date}) return self def __xor__(self, other): return (self | other) - (self & other) def unlink(self): self._log("Delete links: %s" % self) return super(SyncLink, self).unlink() @api.model def _get_eval_context(self): env = self.env def set_link(rel, external_refs, sync_date=None, allow_many2many=False): # Works for external links only return env["sync.link"]._set_link_external(rel, external_refs, sync_date, allow_many2many) def search_links(rel, external_refs): # Works for external links only return env["sync.link"]._search_links_external(rel, external_refs, make_logs=True) def get_link(rel, ref_info): return env["sync.link"]._get_link(rel, ref_info) return { "set_link": set_link, "search_links": search_links, "get_link": get_link, }
class Wizard(models.TransientModel): _name = 'wps.wizard' @api.model def get_default_date_model(self): return pytz.UTC.localize(datetime.now()).astimezone(timezone(self.env.user.tz or 'UTC')) report_file = fields.Char() name = fields.Char(string="File Name") args = [] date = fields.Datetime() time = fields.Datetime() start_date = fields.Date(string='Start Date', required=True) end_date = fields.Date(string="End Date", required=True) days = fields.Integer(string="Days of Payment", readonly=True, store=True) salary_month = fields.Selection([('01', 'January'), ('02', 'February'), ('03', 'March'), ('04', 'April'), ('05', 'May'), ('06', 'June'), ('07', 'July'), ('08', 'August'), ('09', 'September'), ('10', 'October'), ('11', 'November'), ('12', 'December') ], string="Month of Salary", readonly=True) datas = fields.Binary('File', readonly=True) datas_fname = fields.Char('Filename', readonly=True) hr_department_ids = fields.Many2many('hr.department', string='Department(s)') category_ids = fields.Many2many('hr.employee.category', string='Tags') analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account') analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags') payment_method = fields.Many2one('mis.hr.paymentmethod', string="Payment Method") employee_id = fields.Many2one('hr.employee', string='Employee', domian="[('status', '!=', 'draft')]") def _get_hr_tags(self): if self.category_ids: return ('employee_id.category_ids', 'in', self.category_ids.ids) else: return ('company_id', '=', self.env.company.id) def _get_employee(self): if self.employee_id: return ('employee_id', '=', self.employee_id.id) else: return (1, '=', 1) def _get_analytic_account(self): if self.analytic_account_id: return ('employee_id.contract_ids.analytic_account_id', '=', self.analytic_account_id.id) else: return ('company_id', '=', self.env.company.id) def _get_payment_method(self): if self.payment_method: return ('employee_id.payment_method', '=', self.payment_method.id) else: return ('company_id', '=', self.env.company.id) def _get_analytic_tag_ids(self): if self.analytic_tag_ids: return ('employee_id.contract_ids.x_analytic_tag_ids', 'in', self.analytic_tag_ids.ids) else: return ('company_id', '=', self.env.company.id) def _get_department_ids(self): if self.hr_department_ids: return ('employee_id.department_id', 'in', self.hr_department_ids.ids) else: return ('company_id', '=', self.env.company.id) def _get_filtered_domain(self): return [('date_to', '>=', self.start_date), ('date_to', '<=', self.end_date), self._get_hr_tags(), self._get_analytic_account(), self._get_analytic_tag_ids(), self._get_department_ids(), self._get_payment_method(), self._get_employee(), ('company_id', '=', self.env.company.id)] @api.onchange('start_date', 'end_date') def on_date_change(self): if self.start_date and self.end_date: start = str(self.start_date).split('-') end = str(self.end_date).split('-') self.days = 1 + (date(year=int(end[0]), month=int(end[1]), day=int(end[2])) - date(year=int(start[0]), month=int(start[1]), day=int(start[2]))).days if start[1] == end[1]: self.salary_month = start[1] def print_xlsx(self): if not self.env['res.users'].browse(self.env.uid).tz: raise UserError(_('Please Set a User Timezone')) if self.start_date and self.end_date: start = str(self.start_date).split('-') end = str(self.end_date).split('-') # if not start[1] == end[1]: # raise UserError(_('The Dates Can of Same Month Only')) # return self.env['hr.employee'].search(self._get_available_contracts_domain()) slips = self.env['hr.payslip'].search(self._get_filtered_domain()) if not slips: raise UserError(_('There are no payslip Created for the selected month')) user = self.env['res.users'].browse(self.env.uid) if user.tz: tz = pytz.timezone(user.tz) or pytz.utc date = pytz.utc.localize(datetime.now()).astimezone(tz) time = pytz.utc.localize(datetime.now()).astimezone(tz) else: date = datetime.now() time = datetime.now() datetime_string = self.get_default_date_model().strftime("%Y-%m-%d %H:%M:%S") date_string = self.get_default_date_model().strftime("%Y-%m-%d") date_string = self.start_date.strftime("%B-%y") h_date_string = self.start_date.strftime("%B %Y").upper() report_name = 'WPS_' + date.strftime("%y%m%d%H%M%S") filename = '%s %s' % (report_name, date_string) fp = BytesIO() workbook = xlsxwriter.Workbook(fp) wbf = {} wbf['content'] = workbook.add_format() wbf['header1'] = workbook.add_format( {'bold': 1, 'align': 'center', 'font_size': '13'}) wbf['header1'].set_align('vcenter') wbf['header2'] = workbook.add_format( {'bold': 1, 'align': 'center', 'bg_color': '#C4D79B'}) wbf['header2'].set_top() wbf['header2'].set_bottom() wbf['header2'].set_left() wbf['header2'].set_right() wbf['content_float'] = workbook.add_format({'align': 'right', 'num_format': '#,##0.00'}) wbf['content_border'] = workbook.add_format() wbf['content_border'].set_top() wbf['content_border'].set_bottom() wbf['content_border'].set_left() wbf['content_border'].set_right() wbf['content_float_border'] = workbook.add_format({'align': 'right', 'num_format': '#,##0.00'}) wbf['content_float_border'].set_top() wbf['content_float_border'].set_bottom() wbf['content_float_border'].set_left() wbf['content_float_border'].set_right() wbf['content_float_border_bg'] = workbook.add_format( {'align': 'right', 'num_format': '#,##0.00', 'bold': 1, 'bg_color': '#E1E1E1'}) wbf['content_float_border_bg'].set_top() wbf['content_float_border_bg'].set_bottom() wbf['content_float_border_bg'].set_left() wbf['content_float_border_bg'].set_right() wbf['content_signature'] = workbook.add_format({'align': 'center', 'bold': 1}) wbf['content_signature'].set_top(6) worksheet = workbook.add_worksheet(report_name) worksheet2 = workbook.add_worksheet("Summary") worksheet2.center_horizontally() worksheet2.set_margins(left=0, right=0, top=1.5, bottom=1.65) worksheet2.fit_to_pages(1, 0) worksheet2.set_footer('&C-------------------------\n&"Bold"(C.E.O)\n\n'+'&LPage &P of &N', {'margin': .5}) worksheet2.set_row(0, 25) worksheet2.merge_range('A1:E1', 'EMPLOYEES SALARY '+h_date_string, wbf['header1']) col = 0 column_width = 25 worksheet.set_column(col, col, column_width) worksheet.write(0, col, 'Beneficiary Bank Name', wbf['header2']) worksheet2.set_column(col, col, 5) worksheet2.write(1, col, 'SL No', wbf['header2']) col = 1 column_width = 16 worksheet.set_column(col, col, column_width) worksheet.write(0, col, 'Bank Routing Code', wbf['header2']) worksheet2.set_column(col, col, 40) worksheet2.write(1, col, 'EMPLOYEES NAME', wbf['header2']) col = 2 column_width = 15 worksheet.set_column(col, col, column_width) worksheet.write(0, col, 'Transaction Type', wbf['header2']) worksheet2.set_column(col, col, 23.5) worksheet2.write(1, col, 'Beneficiary Bank Name', wbf['header2']) col = 3 column_width = 15 worksheet.set_column(col, col, column_width) worksheet.write(0, col, 'Transaction Code', wbf['header2']) worksheet2.set_column(col, col, 25) worksheet2.write(1, col, 'IBAN NUMBER', wbf['header2']) col = 4 column_width = 40 worksheet.set_column(col, col, column_width) worksheet.write(0, col, 'Beneficiary Name (Max.140 characters)', wbf['header2']) worksheet2.set_column(col, col, 12) worksheet2.write(1, col, 'SALARY AED', wbf['header2']) col = 5 column_width = 25 worksheet.set_column(col, col, column_width) worksheet.write(0, col, 'Beneficiary IBAN No.', wbf['header2']) col = 6 column_width = 12 worksheet.set_column(col, col, column_width) worksheet.write(0, col, 'Amount', wbf['header2']) col = 7 column_width = 21 worksheet.set_column(col, col, column_width) worksheet.write(0, col, 'Remittance Information', wbf['header2']) col = 8 column_width = 14 worksheet.set_column(col, col, column_width) worksheet.write(0, col, 'Transfer Month', wbf['header2']) count = 0 sum = 0 monthyear = self.end_date monthyear = str(monthyear).split('-') monthyear = str(monthyear[1]) + str(monthyear[0]) wps = [] for rec in slips: existing_lines = ( line_id for line_id in wps if line_id['employee'] == rec.employee_id.full_name) main_line = next(existing_lines, False) amount = 0.00 for line in rec.line_ids: if line.code == 'NET': amount = -line.amount if rec.credit_note else line.amount if not main_line: main_line = { 'employee': rec.employee_id.full_name, 'bank': rec.employee_id.agent_id.name, 'routing_code': rec.employee_id.agent_id.routing_code, 'iban_no': rec.employee_id.iban_number, 'amount': amount } wps.append(main_line) else: main_line['amount'] += amount for rec in wps: if rec['amount'] != 0: count += 1 col = 0 worksheet2.write(count + 1, col, count, wbf['content_border']) if rec['bank']: worksheet.write(count, col, rec['bank'], wbf['content_border']) worksheet2.write(count + 1, 2, rec['bank'], wbf['content_border']) else: worksheet2.write(count + 1, 2, '', wbf['content_border']) col += 1 if rec['routing_code']: worksheet.write(count, col, rec['routing_code'], wbf['content_border']) col += 1 worksheet.write(count, col, "Salary", wbf['content_border']) col += 1 worksheet.write(count, col, "SAL", wbf['content_border']) col += 1 worksheet.write(count, col, rec['employee'], wbf['content_border']) worksheet2.write(count + 1, 1, rec['employee'], wbf['content_border']) col += 1 if rec['iban_no']: worksheet.write(count, col, rec['iban_no'], wbf['content_border']) worksheet2.write(count + 1, 3, rec['iban_no'], wbf['content_border']) else: worksheet2.write(count + 1, 3, '', wbf['content_border']) col += 1 worksheet.write(count, col, rec['amount'], wbf['content_float_border']) worksheet2.write(count + 1, 4, rec['amount'], wbf['content_float_border']) sum += rec['amount'] col += 1 worksheet.write(count, col, "Salary Transfer", wbf['content_border']) col += 1 worksheet.write(count, col, date_string, wbf['content_border']) count += 2 worksheet2.merge_range('A%s:D%s' % (count + 1, count + 1), 'Total', wbf['content_float_border_bg']) worksheet2.write(count, 4, sum, wbf['content_float_border_bg']) workbook.close() out = base64.encodebytes(fp.getvalue()) self.write({'datas': out, 'datas_fname': filename}) fp.close() filename += '%2Exlsx' return { 'type': 'ir.actions.act_url', 'target': 'new', 'url': 'web/content/?model=' + self._name + '&id=' + str( self.id) + '&field=datas&download=true&filename=' + filename, }
class battle(models.Model): _name = 'game.battle' name = fields.Char(compute='_get_name') attack = fields.Many2many('res.partner', relation='player_attacks', column1='b_a', column2='p_a', domain="[('is_player','!=',True)]" ) # Cal especificar el nom de la taula defend = fields.Many2many('res.partner', relation='player_defends', column1='b_d', column2='p_d', domain="[('is_player','!=',True)]") characters_attack_available = fields.Many2many( 'game.character', compute='_get_characters_available') characters_defend_available = fields.Many2many( 'game.character', compute='_get_characters_available') characters_attack = fields.Many2many( 'game.character', relation='characters_attack', domain="[('id', 'in', characters_attack_available)]") characters_defend = fields.Many2many( 'game.character', relation='characters_defend', domain="[('id', 'in', characters_defend_available)]") state = fields.Selection([('1', 'Creation'), ('2', 'Character Selection'), ('3', 'Waiting'), ('4', 'Finished')], compute='_get_state') time_remaining = fields.Char(compute='_get_state') def _get_date(self): date = datetime.now() + timedelta(hours=3) return fields.Datetime.to_string(date) date = fields.Datetime( default=_get_date) # Serà calculada a partir de l'hora de creació finished = fields.Boolean() def _get_characters_available(self): for c in self: c.characters_attack_available = c.attack.mapped( 'fortresses').mapped('characters').filtered( lambda p: p.unemployed == True and p.health > 0) c.characters_defend_available = c.defend.mapped( 'fortresses').mapped('characters').filtered( lambda p: p.unemployed == True and p.health > 0) @api.onchange('attack', 'defend') def onchange_attack(self): for b in self: characters_available = b.attack.mapped('fortresses').mapped( 'characters').filtered( lambda p: p.unemployed == True and p.health > 0) characters_available_defense = b.defend.mapped( 'fortresses').mapped('characters').filtered( lambda p: p.unemployed == True and p.health > 0) b.characters_attack_available = characters_available b.characters_defend_available = characters_available_defense attackers = self.attack defenders = self.defend result = {} if len(attackers) > 0 and len(defenders) > 0: # Tots els atacants i defensors han d'estar en el mateix clan if len(attackers.mapped('clan')) > 1: warning = { 'title': "Battle not valid", 'message': 'All attackers have to be the same clan' } result['warning'] = warning return result if len(defenders.mapped('clan')) > 1: warning = { 'title': "Battle not valid", 'message': 'All attackers have to be the same clan' } result['warning'] = warning return result if defenders[0].clan == attackers[0].clan: warning = { 'title': "Battle not valid", 'message': 'One clan cannot attack himself' } result['warning'] = warning return result # Tots els caracters atacants ha de ser de jugadors atacants if len( self.characters_defend ) > 0 and defenders.ids != self.characters_defend.mapped( 'player.id'): warning = { 'title': "Battle not valid", 'message': 'All the characters have to be from defender players' } result['warning'] = warning return result if len( self.characters_attack ) > 0 and attackers.ids != self.characters_attack.mapped( 'player.id'): warning = { 'title': "Battle not valid", 'message': 'All the characters have to be from attacker players' } result['warning'] = warning return result # En cas de superar tots els tests result = { 'domain': { 'characters_attack': [('id', 'in', characters_available.ids)], 'characters_defend': [('id', 'in', characters_available_defense.ids)], 'attack': [('id', 'not in', b.defend.ids), ('is_player', '=', True)], 'defend': [('id', 'not in', b.attack.ids), ('is_player', '=', True)], #'attack': [('clan', 'in', b.attack.mapped('clan'))], 'defend': [('clan', 'in', b.defend.mapped('clan'))], }, } if len(attackers) > 0: result['domain']['attack'] = [ ('id', 'not in', b.defend.ids), ('clan', 'in', b.attack.mapped('clan').ids), ('clan', 'not in', b.defend.mapped('clan').ids) ] if len(defenders) > 0: result['domain']['defend'] = [ ('id', 'not in', b.attack.ids), ('clan', 'in', b.defend.mapped('clan').ids), ('clan', 'not in', b.attack.mapped('clan').ids) ] return result @api.depends('attack', 'defend') def _get_name(self): for b in self: name = '' for i in b.attack: name = name + i.name + ', ' name = name + ' VS ' for i in b.defend: name = name + i.name + ', ' b.name = name def _get_state(self): for b in self: b.state = '1' if len(b.attack) > 0 and len(b.defend) > 0: b.state = '2' if len(b.characters_attack) > 0 and len(b.characters_defend) > 0: b.state = '3' if b.finished == True: b.state = '4' start = datetime.now() end = fields.Datetime.from_string(b.date) relative = relativedelta(end, start) if end > start: b.time_remaining = str(relative.hours) + ":" + str( relative.minutes) else: b.time_remaining = '00:00' def compute_battle(self): for b in self: at = b.characters_attack.ids # Els atacants print(at) de = b.characters_defend.ids # Els Defensors print(de) finished = False first = True # La primera ronda es amb dispars, la resta es melee rounds = 1 while (not finished): # L'ordre de la batalla és important, # ja que els primers són els primers en atacar defendre for i in zip(at, de): c_at = self.env['game.character'].browse( i[0]) # El caracter atacant c_de = self.env['game.character'].browse(i[1]) b.calculate_attack(c_at, c_de, first) b.calculate_attack(c_de, c_at, first) at = b.characters_attack.filtered(lambda c: c.health > 0).ids de = b.characters_defend.filtered(lambda c: c.health > 0).ids first = False if len(at) == 0 or len(de) == 0: finished = True else: rounds = rounds + 1 # Ja ha acabat, ara a repartir experiencia i botin de guerra if len(at) == 0: wins = 'Defenders (' + str(len(de)) + ' survivors)' for d in self.env['game.character'].browse(de): d.write({'war': d.war + 100}) botin = b.characters_attack.mapped('stuff') players = b.defend.ids for s in botin: player = random.choice(players) s.write({'character': False, 'player': player}) else: wins = 'Attackers (' + str(len(at)) + ' survivors)' for a in self.env['game.character'].browse(at): a.write({ 'war': a.war + 100 }) # Augmenta la experiència en guerra de l'atacant botin = b.characters_defend.mapped( 'stuff') # Totes les armes passen al botin de guerra players = b.attack.ids for s in botin: player = random.choice(players) s.write({'character': False, 'player': player}) # L'atacant ataca per obtindre recursos del defensor first_defender = b.defend[0] #print(first_defender) for r in first_defender.raws: raw = r.raw.id raws = b.attack[0].clan.raws.search([ ('raw', '=', raw), ('clan', '=', b.attack[0].clan.id) ])[0] #print(raws) raws.write({ 'quantity': r.quantity / 2 }) # El clan obté la meitat dels recursos del defensor # Anem a fer que alguns dels caiguts siguen ferits i no morts death = (b.characters_attack + b.characters_defend).filtered(lambda c: c.health == 0) for d in death: if random.random() > 0.5: # 50% de que no estiga mort d.write({ 'health': 1, 'war': d.war + 50 }) # Augmenta la seua experiencia en guerra self.env['game.log'].create({ 'title': 'Battle ' + str(b.name), 'description': wins + ' wins in ' + str(rounds) + ' rounds' }) b.write({'finished': True}) def calculate_attack(self, c_at, c_de, first): print('Ataca: ' + c_at.name + ' Defen: ' + c_de.name) # El nivell de 'war' indica quin dels dos caracters # Està més experimentat. Això li dona un avantatge percentual ratio_at_de = c_at.war / (c_at.war + c_de.war) ratio_de_at = c_de.war / (c_at.war + c_de.war) print("Ratios: At. War: " + str(c_at.war) + " Def War: " + str(c_de.war) + " Ratio At: " + str(ratio_at_de) + " Ration Def: " + str(ratio_de_at)) if first: # Calcular en base al shot at_best_shot = c_at.stuff.filtered( lambda s: s.type == '0') # Si va equipat en armes de foc if at_best_shot: at_best_shot = at_best_shot.sorted( key=lambda s: s.shoot, reverse=True)[0].shoot # La millor arma de foc else: at_best_shot = 1 else: # Calcular en base al melee at_best_shot = c_at.stuff.filtered( lambda s: s.type == '1') # Si va equipat en armes de melee if at_best_shot: at_best_shot = at_best_shot.sorted( key=lambda s: s.melee, reverse=True)[0].melee # La millor arma de foc else: at_best_shot = 1 de_best_armor = c_de.stuff.filtered( lambda s: s.type == '2') # Si te armadura if de_best_armor: de_best_armor = de_best_armor.sorted( key=lambda s: s.armor, reverse=True)[0].armor # La millor armadura else: de_best_armor = 1 ratio_shot_armor = at_best_shot / (at_best_shot + de_best_armor ) # El ratio de arma/armadur print("Ratio Shot/armor: " + str(ratio_shot_armor)) ratio_at_de = ratio_at_de * ratio_shot_armor print("Ratio final: " + str(ratio_at_de)) if random.random( ) < ratio_at_de: # quan més ratio mes probabilitat de fer mal damage_de = 100 * ratio_at_de # Com que el ratio no pot ser > 1, el mal no pot ser > 100 print('LI FA MAL') else: damage_de = 0 print("Li fa este mal: " + str(damage_de)) health_de = c_de.health - damage_de if health_de < 0: health_de = 0 c_de.write({'health': health_de})
class HotelReservationOrder(models.Model): @api.multi @api.depends('order_list') def _sub_total(self): ''' amount_subtotal will display on change of order_list ---------------------------------------------------- @param self: object pointer ''' for sale in self: sale.amount_subtotal = sum(line.price_subtotal for line in sale.order_list) @api.multi @api.depends('amount_subtotal') def _total(self): ''' amount_total will display on change of amount_subtotal ------------------------------------------------------- @param self: object pointer ''' for line in self: line.amount_total = line.amount_subtotal + (line.amount_subtotal * line.tax) / 100.0 @api.multi def reservation_generate_kot(self): """ This method create new record for hotel restaurant order list. -------------------------------------------------------------- @param self: The object pointer @return: new record set for hotel restaurant order list. """ res = [] order_tickets_obj = self.env['hotel.restaurant.kitchen.order.tickets'] rest_order_list_obj = self.env['hotel.restaurant.order.list'] for order in self: if len(order.order_list) == 0: raise except_orm(_('No Order Given'), _('Please Give an Order')) table_ids = [x.id for x in order.table_no] line_data = { 'orderno': order.order_number, 'resno': order.reservationno.reservation_id, 'kot_date': order.date1, 'w_name': order.waitername.name, 'tableno': [(6, 0, table_ids)], } kot_data = order_tickets_obj.create(line_data) self.kitchen_id = kot_data.id for order_line in order.order_list: o_line = { 'kot_order_list': kot_data.id, 'name': order_line.name.id, 'item_qty': order_line.item_qty, 'item_rate': order_line.item_rate } rest_order_list_obj.create(o_line) res.append(order_line.id) self.rest_id = [(6, 0, res)] self.state = 'order' return res @api.multi def reservation_update_kot(self): """ This method update record for hotel restaurant order list. ---------------------------------------------------------- @param self: The object pointer @return: update record set for hotel restaurant order list. """ order_tickets_obj = self.env['hotel.restaurant.kitchen.order.tickets'] rest_order_list_obj = self.env['hotel.restaurant.order.list'] for order in self: table_ids = [x.id for x in order.table_no] line_data = { 'orderno': order.order_number, 'resno': order.reservationno.reservation_id, 'kot_date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'w_name': order.waitername.name, 'tableno': [(6, 0, table_ids)], } kot_obj = order_tickets_obj.browse(self.kitchen_id) kot_obj.write(line_data) for order_line in order.order_list: if order_line.id not in order.rest_id.ids: kot_data1 = order_tickets_obj.create(line_data) self.kitchen_id = kot_data1.id o_line = { 'kot_order_list': kot_data1.id, 'name': order_line.name.id, 'item_qty': order_line.item_qty, 'item_rate': order_line.item_rate } self.rest_id = [(4, order_line.id)] rest_order_list_obj.create(o_line) return True @api.multi def done_kot(self): """ This method is used to change the state to done of the hotel reservation order ---------------------------------------- @param self: object pointer """ hotel_folio_obj = self.env['hotel.folio'] hsl_obj = self.env['hotel.service.line'] so_line_obj = self.env['sale.order.line'] for order_obj in self: hotelfolio = order_obj.folio_id.order_id.id if order_obj.folio_id: for order1 in order_obj.order_list: values = {'order_id': hotelfolio, 'name': order1.name.name, 'product_id': order1.name.product_id.id, 'product_uom_qty': order1.item_qty, 'price_unit': order1.item_rate, 'price_subtotal': order1.price_subtotal, } sol_rec = so_line_obj.create(values) hsl_obj.create({'folio_id': order_obj.folio_id.id, 'service_line_id': sol_rec.id}) hf_rec = hotel_folio_obj.browse(order_obj.folio_id.id) hf_rec.write({'hotel_reservation_order_ids': [(4, order_obj.id)]}) if order_obj.reservationno: order_obj.reservationno.write({'state': 'done'}) self.state = 'done' return True _name = "hotel.reservation.order" _description = "Reservation Order" _rec_name = "order_number" order_number = fields.Char('Order No', size=64, readonly=True) reservationno = fields.Many2one('hotel.restaurant.reservation', 'Reservation No') date1 = fields.Datetime('Date', required=True, default=(lambda *a: time.strftime (DEFAULT_SERVER_DATETIME_FORMAT))) waitername = fields.Many2one('res.partner', 'Waiter Name') table_no = fields.Many2many('hotel.restaurant.tables', 'temp_table4', 'table_no', 'name', 'Table Number') order_list = fields.One2many('hotel.restaurant.order.list', 'o_l', 'Order List') tax = fields.Float('Tax (%) ', size=64) amount_subtotal = fields.Float(compute='_sub_total', method=True, string='Subtotal') amount_total = fields.Float(compute='_total', method=True, string='Total') kitchen_id = fields.Integer('Kitchen id') rest_id = fields.Many2many('hotel.restaurant.order.list', 'reserv_id', 'kitchen_id', 'res_kit_ids', "Rest") state = fields.Selection([('draft', 'Draft'), ('order', 'Order Created'), ('done', 'Done')], 'State', index=True, required=True, readonly=True, default=lambda * a: 'draft') folio_id = fields.Many2one('hotel.folio', string='Folio No') is_folio = fields.Boolean('Is a Hotel Guest??', help='is customer reside' 'in hotel or not') @api.model def create(self, vals): """ Overrides orm create method. @param self: The object pointer @param vals: dictionary of fields value. """ if not vals: vals = {} if self._context is None: self._context = {} seq_obj = self.env['ir.sequence'] res_oder = seq_obj.next_by_code('hotel.reservation.order') or 'New' vals['order_number'] = res_oder return super(HotelReservationOrder, self).create(vals)
class AeatCheckSiiResult(models.Model): _name = 'aeat.check.sii.result' RESULTS = [('ConDatos', 'Con datos'), ('SinDatos', 'Sin datos')] RECONCILE = [('1', 'No contrastable'), ('2', 'En proceso de contraste'), ('3', 'No contrastada'), ('4', 'Parcialmente contrastada'), ('5', 'Contrastada')] check_date = fields.Datetime(string='Date check') result_query = fields.Selection(RESULTS, string='Type') vat = fields.Char(string='NIF') other_id = fields.Char(string='Other identifier') invoice_number = fields.Char(string='Invoice number') invoice_date = fields.Date(string='Invoice date') invoice_type = fields.Char(string='Invoice type') refund_type = fields.Selection( selection=[('S', 'By substitution'), ('I', 'By differences')], string="Refund Type") registration_key = fields.Many2one( comodel_name='aeat.sii.mapping.registration.keys', string="Registration key") amount_total = fields.Float( string='Amount total', digits=dp.get_precision('Account')) description = fields.Text(string='Description') name = fields.Char(string='Company Name') vat_partner = fields.Char(string='NIF') other_id_partner = fields.Char(string='Other identifier') vat_presenter = fields.Char(string='NIF') timestamp_presentation = fields.Datetime(string='Timestamp Presentation') csv = fields.Char(string='CSV') reconcile_state = fields.Selection(RECONCILE, string='Reconcile State') reconcile_timestamp = fields.Datetime(string='Reconcile Timestamp') timestamp_last = fields.Datetime(string='Timestamp Last Update ') sent_state = fields.Char(string='State') error_code = fields.Char(string='Error code') description_error = fields.Text(string='Description Error') reconcile_description = fields.Text(string='Reconcile description') registry_error_description = fields.Char( string='Registry Error Description') invoice_id = fields.Many2one(comodel_name='account.move', string='Invoice') def _get_data(self, model_id, res, model): data = {} key = '' key_type = '' if model == 'account.move': if model_id.type in ('out_invoice', 'out_refund'): data = res['RegistroRespuestaConsultaLRFacturasEmitidas'][0] key = 'DatosFacturaEmitida' key_type = 'sale' if model_id.type in ('in_invoice', 'in_refund'): data = res['RegistroRespuestaConsultaLRFacturasRecibidas'][0] key = 'DatosFacturaRecibida' key_type = 'purchase' return data, key, key_type def _prepare_vals(self, model_id, res, fault, model): vals = { 'check_date': fields.Datetime.now() } if model == 'account.move': vals['invoice_id'] = model_id.id if fault: vals['registry_error_description'] = fault else: vals['result_query'] = res['ResultadoConsulta'] data, key, key_type = self._get_data(model_id, res, model) if 'result_query' in vals and vals['result_query'] == 'ConDatos': key_obj = self.env['aeat.sii.mapping.registration.keys'] vals['vat'] = data['IDFactura']['IDEmisorFactura']['NIF'] if model == 'account.move': if model_id.type in ('in_invoice', 'in_refund'): vals['other_id'] = data[ 'IDFactura']['IDEmisorFactura']['IDOtro'] vals['invoice_number'] = data['IDFactura']['NumSerieFacturaEmisor'] date = datetime.datetime.strptime( data['IDFactura']['FechaExpedicionFacturaEmisor'], '%d-%m-%Y') new_date = datetime.datetime.strftime( date, '%Y-%m-%d') vals['invoice_date'] = new_date vals['invoice_type'] = data[key]['TipoFactura'] vals['refund_type'] = data[key]['TipoRectificativa'] registration_key = key_obj.search( [('code', '=', data[key][ 'ClaveRegimenEspecialOTrascendencia']), ('type', '=', key_type)] ) vals['registration_key'] = registration_key.id vals['amount_total'] = data[key]['ImporteTotal'] vals['description'] = data[key]['DescripcionOperacion'] if 'Contraparte' in data[key] and data[key]['Contraparte']: vals['name'] = data[key]['Contraparte']['NombreRazon'] vals['vat_partner'] = data[key]['Contraparte']['NIF'] vals['other_id_partner'] = data[key]['Contraparte']['IDOtro'] vals['vat_presenter'] = data['DatosPresentacion']['NIFPresentador'] date = datetime.datetime.strptime( data['DatosPresentacion']['TimestampPresentacion'], '%d-%m-%Y %H:%M:%S') new_date = datetime.datetime.strftime( date, '%Y-%m-%d %H:%M:%S') vals['timestamp_presentation'] = new_date vals['csv'] = data['DatosPresentacion']['CSV'] vals['reconcile_state'] = data['EstadoFactura']['EstadoCuadre'] model_id.sii_reconcile_state = vals['reconcile_state'] if 'TimestampEstadoCuadre' in data['EstadoFactura']: if data['EstadoFactura']['TimestampEstadoCuadre']: date = datetime.datetime.strptime( data['EstadoFactura']['TimestampEstadoCuadre'], '%d-%m-%Y %H:%M:%S') new_date = datetime.datetime.strftime( date, '%Y-%m-%d %H:%M:%S') vals['reconcile_timestamp'] = new_date date = datetime.datetime.strptime( data['EstadoFactura']['TimestampUltimaModificacion'], '%d-%m-%Y %H:%M:%S') new_date = datetime.datetime.strftime( date, '%Y-%m-%d %H:%M:%S') vals['timestamp_last'] = new_date vals['sent_state'] = data['EstadoFactura']['EstadoRegistro'] vals['error_code'] = data['EstadoFactura']['CodigoErrorRegistro'] vals['description_error'] = data[ 'EstadoFactura']['DescripcionErrorRegistro'] vals['reconcile_description'] = data['DatosDescuadreContraparte'] return vals def create_result(self, model_id, res, fault, model): vals = self._prepare_vals(model_id, res, fault, model) self.create(vals)
class HotelRestaurantReservation(models.Model): @api.multi def create_order(self): """ This method is for create a new order for hotel restaurant reservation .when table is booked and create order button is clicked then this method is called and order is created.you can see this created order in "Orders" ------------------------------------------------------------ @param self: The object pointer @return: new record set for hotel restaurant reservation. """ proxy = self.env['hotel.reservation.order'] for record in self: table_ids = [tableno.id for tableno in record.tableno] values = { 'reservationno': record.id, 'date1': record.start_date, 'folio_id': record.folio_id.id, 'table_no': [(6, 0, table_ids)], 'is_folio': record.is_folio, } proxy.create(values) self.state = 'order' return True @api.onchange('cname') def onchange_partner_id(self): ''' When Customer name is changed respective adress will display in Adress field @param self: object pointer ''' if not self.cname: self.partner_address_id = False else: addr = self.cname.address_get(['default']) self.partner_address_id = addr['default'] @api.onchange('folio_id') def get_folio_id(self): ''' When you change folio_id, based on that it will update the cname and room_number as well --------------------------------------------------------- @param self: object pointer ''' for rec in self: self.cname = False self.room_no = False if rec.folio_id: self.cname = rec.folio_id.partner_id.id if rec.folio_id.room_lines: self.room_no = rec.folio_id.room_lines[0].product_id.id @api.multi def action_set_to_draft(self): """ This method is used to change the state to draft of the hotel restaurant reservation -------------------------------------------- @param self: object pointer """ self.state = 'draft' return True @api.multi def table_reserved(self): """ when CONFIRM BUTTON is clicked this method is called for table reservation @param self: The object pointer @return: change a state depending on the condition """ for reservation in self: self._cr.execute("select count(*) from " "hotel_restaurant_reservation as hrr " "inner join reservation_table as rt on \ rt.reservation_table_id = hrr.id " "where (start_date,end_date)overlaps\ ( timestamp %s , timestamp %s ) " "and hrr.id<> %s and state != 'done'" "and rt.name in (select rt.name from \ hotel_restaurant_reservation as hrr " "inner join reservation_table as rt on \ rt.reservation_table_id = hrr.id " "where hrr.id= %s) ", (reservation.start_date, reservation.end_date, reservation.id, reservation.id)) res = self._cr.fetchone() roomcount = res and res[0] or 0.0 if len(reservation.tableno.ids) == 0: raise except_orm(_('Warning'), _('Please Select Tables For Reservation')) if roomcount: raise except_orm(_('Warning'), _('You tried to confirm \ reservation with table those already reserved in this \ reservation period')) else: self.state = 'confirm' return True @api.multi def table_cancel(self): """ This method is used to change the state to cancel of the hotel restaurant reservation -------------------------------------------- @param self: object pointer """ self.state = 'cancel' return True @api.multi def table_done(self): """ This method is used to change the state to done of the hotel restaurant reservation -------------------------------------------- @param self: object pointer """ self.state = 'done' return True _name = "hotel.restaurant.reservation" _description = "Includes Hotel Restaurant Reservation" _rec_name = "reservation_id" reservation_id = fields.Char('Reservation No', size=64, readonly=True, index=True) room_no = fields.Many2one('product.product', string='Room No', size=64, index=True) folio_id = fields.Many2one('hotel.folio', string='Folio No') start_date = fields.Datetime('Start Time', required=True, default=(lambda *a: time.strftime (DEFAULT_SERVER_DATETIME_FORMAT))) end_date = fields.Datetime('End Time', required=True) cname = fields.Many2one('res.partner', string='Customer Name', size=64, required=True, index=True) partner_address_id = fields.Many2one('res.partner', string='Address') tableno = fields.Many2many('hotel.restaurant.tables', relation='reservation_table', index=True, column1='reservation_table_id', column2='name', string='Table Number', help="Table reservation detail. ") state = fields.Selection([('draft', 'Draft'), ('confirm', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled'), ('order', 'Order Created')], 'state', index=True, required=True, readonly=True, default=lambda * a: 'draft') is_folio = fields.Boolean('Is a Hotel Guest??') @api.model def create(self, vals): """ Overrides orm create method. @param self: The object pointer @param vals: dictionary of fields value. """ if not vals: vals = {} if self._context is None: self._context = {} seq_obj = self.env['ir.sequence'] resrve = seq_obj.next_by_code('hotel.restaurant.reservation') or 'New' vals['reservation_id'] = resrve return super(HotelRestaurantReservation, self).create(vals) @api.constrains('start_date', 'end_date') def check_start_dates(self): ''' This method is used to validate the start_date and end_date. ------------------------------------------------------------- @param self: object pointer @return: raise a warning depending on the validation ''' if self.start_date >= self.end_date: raise ValidationError(_('Start Date Should be less \ than the End Date!'))
class SaasServerClient(models.Model): _name = 'saas_server.client' _inherit = ['mail.thread', 'saas_base.client'] name = fields.Char('Database name', readonly=True, required=True) client_id = fields.Char('Database UUID', readonly=True, index=True) expiration_datetime = fields.Datetime(readonly=True) state = fields.Selection([('template', 'Template'), ('draft', 'New'), ('open', 'In Progress'), ('cancelled', 'Cancelled'), ('pending', 'Pending'), ('deleted', 'Deleted')], 'State', default='draft', track_visibility='onchange') host = fields.Char('Host') _sql_constraints = [ ('client_id_uniq', 'unique (client_id)', 'client_id should be unique!'), ] @api.multi def create_database(self, template_db=None, demo=False, lang='en_US'): self.ensure_one() new_db = self.name res = {} if template_db: db._drop_conn(self.env.cr, template_db) db.exp_duplicate_database(template_db, new_db) else: password = random_password() res.update({'superuser_password': password}) db.exp_create_database(new_db, demo, lang, user_password=password) self.state = 'open' return res @api.multi def registry(self, new=False, **kwargs): self.ensure_one() m = odoo.modules.registry.Registry return m.new(self.name, **kwargs) @api.multi def install_addons(self, addons, is_template_db): self.ensure_one() addons = set(addons) addons.add('mail_delete_sent_by_footer') # debug if is_template_db: addons.add('auth_oauth') addons.add('saas_client') else: addons.add('saas_client') if not addons: return with self.registry().cursor() as cr: env = api.Environment(cr, SUPERUSER_ID, self._context) self._install_addons(env, addons) @api.multi def disable_mail_servers(self): ''' disables mailserver on db to stop it from sending and receiving mails ''' # let's disable incoming mail servers self.ensure_one() incoming_mail_servers = self.env['fetchmail.server'].search([]) if len(incoming_mail_servers): incoming_mail_servers.write({'active': False}) # let's disable outgoing mailservers too outgoing_mail_servers = self.env['ir.mail_server'].search([]) if len(outgoing_mail_servers): outgoing_mail_servers.write({'active': False}) @api.multi def _install_addons(self, client_env, addons): for addon in client_env['ir.module.module'].search([('name', 'in', list(addons))]): addon.button_install() @api.multi def update_registry(self): self.ensure_one() self.registry(new=True, update_module=True) @api.multi def prepare_database(self, **kwargs): self.ensure_one() with self.registry().cursor() as cr: env = api.Environment(cr, SUPERUSER_ID, self._context) self._prepare_database(env, **kwargs) @api.model def _config_parameters_to_copy(self): return [ 'saas_client.ab_location', 'saas_client.ab_register', 'saas_client.saas_dashboard' ] @api.multi def _prepare_database(self, client_env, owner_user=None, is_template_db=False, addons=None, access_token=None, tz=None, server_requests_scheme='http'): self.ensure_one() if not addons: addons = [] client_id = self.client_id # update saas_server.client state if is_template_db: self.state = 'template' # set tz if tz: client_env['res.users'].search([]).write({'tz': tz}) client_env['ir.default'].set('res.partner', 'tz', tz) # update database.uuid client_env['ir.config_parameter'].sudo().set_param( 'database.uuid', client_id) # copy configs for key in self._config_parameters_to_copy(): value = self.env['ir.config_parameter'].sudo().get_param( key, default='') client_env['ir.config_parameter'].sudo().set_param(key, value) # set web.base.url config client_env['ir.config_parameter'].sudo().set_param( 'web.base.url', '%s://%s' % (server_requests_scheme, self.host)) # saas_client must be already installed oauth_provider = client_env.ref('saas_client.saas_oauth_provider') if is_template_db: # copy auth provider from saas_server saas_oauth_provider = self.env.ref( 'saas_server.saas_oauth_provider') oauth_provider_data = {'enabled': False, 'client_id': client_id} for attr in [ 'name', 'auth_endpoint', 'scope', 'validation_endpoint', 'data_endpoint', 'css_class', 'body', 'enabled', 'local_host', 'local_port' ]: oauth_provider_data[attr] = getattr(saas_oauth_provider, attr) oauth_provider = client_env.ref('saas_client.saas_oauth_provider') oauth_provider.write(oauth_provider_data) else: oauth_provider.client_id = client_id # prepare users OWNER_TEMPLATE_LOGIN = '******' user = None if is_template_db: client_env['res.users'].create({ 'login': OWNER_TEMPLATE_LOGIN, 'name': 'NAME', 'email': '*****@*****.**', }) client_env['res.users'].browse(SUPERUSER_ID).write({ 'oauth_provider_id': oauth_provider.id, 'oauth_uid': SUPERUSER_ID, 'oauth_access_token': access_token }) else: domain = [('login', '=', OWNER_TEMPLATE_LOGIN)] res = client_env['res.users'].search(domain) if res: user = res[0] client_env['ir.config_parameter'].sudo().set_param( 'res.users.owner', user.id) portal_owner_uid = owner_user.pop('user_id') res = client_env['res.users'].search([('oauth_uid', '=', portal_owner_uid)]) if res: # user already exists (e.g. administrator) user = res[0] if not user: user = client_env['res.users'].browse(SUPERUSER_ID) vals = owner_user vals.update({ 'oauth_provider_id': oauth_provider.id, 'oauth_uid': portal_owner_uid, 'oauth_access_token': access_token, 'country_id': owner_user.get('country_id') and self.env['res.country'].browse(owner_user['country_id']) and self.env['res.country'].browse( owner_user['country_id']).id, }) user.write(vals) @api.model def update_all(self): self.sudo().search([]).update() @api.multi def update_one(self): for server in self: server.sudo().update() @api.multi def update(self): for record in self: try: registry = record.registry() with registry.cursor() as client_cr: client_env = api.Environment(client_cr, SUPERUSER_ID, record._context) data = record._get_data(client_env, record.client_id) record.write(data) except psycopg2.OperationalError: if record.state != 'draft': record.state = 'deleted' return @api.multi def _get_data(self, client_env, check_client_id): self.ensure_one() client_id = client_env['ir.config_parameter'].sudo().get_param( 'database.uuid') if check_client_id != client_id: return {'state': 'deleted'} users = client_env['res.users'].search([('share', '=', False), ('id', '!=', SUPERUSER_ID)]) param_obj = client_env['ir.config_parameter'] max_users = param_obj.sudo().get_param('saas_client.max_users', '0').strip() suspended = param_obj.sudo().get_param('saas_client.suspended', '0').strip() total_storage_limit = param_obj.sudo().get_param( 'saas_client.total_storage_limit', '0').strip() users_len = len(users) data_dir = odoo.tools.config['data_dir'] file_storage = get_size('%s/filestore/%s' % (data_dir, self.name)) file_storage = int(file_storage / (1024 * 1024)) client_env.cr.execute("select pg_database_size('%s')" % self.name) db_storage = client_env.cr.fetchone()[0] db_storage = int(db_storage / (1024 * 1024)) data = { 'client_id': client_id, 'users_len': users_len, 'max_users': max_users, 'file_storage': file_storage, 'db_storage': db_storage, 'total_storage_limit': total_storage_limit, } if suspended == '0' and self.state in ('pending', 'deleted'): data.update({'state': 'open'}) if suspended == '1' and self.state in ('open', 'deleted'): data.update({'state': 'pending'}) return data @api.multi def upgrade_database(self, **kwargs): for record in self.filtered(lambda record: record.state != "deleted"): with record.registry().cursor() as cr: env = api.Environment(cr, SUPERUSER_ID, record._context) return record._upgrade_database(env, **kwargs) @api.multi def _upgrade_database(self, client_env, data): self.ensure_one() # "data" comes from saas_portal/models/wizard.py::upgrade_database post = data module = client_env['ir.module.module'] print(('_upgrade_database', data)) res = {} # 0. Update module list update_list = post.get('update_addons_list', False) if update_list: module.update_list() # 1. Update addons update_addons = post.get('update_addons', []) if update_addons: module.search([('name', 'in', update_addons) ]).button_immediate_upgrade() # 2. Install addons install_addons = post.get('install_addons', []) if install_addons: module.search([('name', 'in', install_addons) ]).button_immediate_install() # 3. Uninstall addons uninstall_addons = post.get('uninstall_addons', []) if uninstall_addons: module.search([('name', 'in', uninstall_addons) ]).button_immediate_uninstall() # 4. Run fixes fixes = post.get('fixes', []) for model, method in fixes: getattr(self.env[model], method)() # 5. update parameters params = post.get('params', []) for obj in params: if obj['key'] == 'saas_client.expiration_datetime': self.expiration_datetime = obj['value'] if obj['key'] == 'saas_client.trial' and obj['value'] == 'False': self.trial = False # groups = [] # if obj.get('hidden'): # groups = ['saas_client.group_saas_support'] client_env['ir.config_parameter'].sudo().set_param( obj['key'], obj['value'] or ' ') # 6. Access rights access_owner_add = post.get('access_owner_add', []) owner_id = client_env['ir.config_parameter'].sudo().get_param( 'res.users.owner', 0) owner_id = int(owner_id) if not owner_id: res['owner_id'] = "Owner's user is not found" if access_owner_add and owner_id: res['access_owner_add'] = [] for g_ref in access_owner_add: g = client_env.ref(g_ref, raise_if_not_found=False) if not g: res['access_owner_add'].append('group not found: %s' % g_ref) continue g.write({'users': [(4, owner_id, 0)]}) access_remove = post.get('access_remove', []) if access_remove: res['access_remove'] = [] for g_ref in access_remove: g = client_env.ref(g_ref, raise_if_not_found=False) if not g: res['access_remove'].append('group not found: %s' % g_ref) continue users = [] for u in g.users: if u.id != SUPERUSER_ID: users.append((3, u.id, 0)) g.write({'users': users}) # 7. Configure outgoing mail data = post.get('configure_outgoing_mail', []) for mail_conf in data: ir_mail_server = client_env['ir.mail_server'] ir_mail_server.create({ 'name': 'mailgun', 'smtp_host': 'smtp.mailgun.org', 'smtp_user': mail_conf['smtp_login'], 'smtp_pass': mail_conf['smtp_password'] }) # 8.Limit number of records model_obj = client_env['ir.model'] base_limit_records_number_obj = client_env['base.limit.records_number'] data = post.get('limit_nuber_of_records', []) for limit_line in data: model = model_obj.search([('model', '=', limit_line['model'])]) if model: limit_record = base_limit_records_number_obj.search([ ('model_id', '=', model.id) ]) if limit_record: limit_record.update({ 'domain': limit_line['domain'], 'max_records': limit_line['max_records'], }) else: base_limit_records_number_obj.create({ 'name': 'limit_' + limit_line['model'], 'model_id': model.id, 'domain': limit_line['domain'], 'max_records': limit_line['max_records'], }) else: res['limit'] = "there is no model named %s" % limit_line[ 'model'] return res @api.model def _cron_delete_expired_databases(self): now = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) res = self.search([('state', 'not in', ['deleted', 'template']), ('expiration_datetime', '<=', now), ('trial', '=', True)]) _logger.info('delete_expired_databases %s', res) res.delete_database() @api.multi def delete_database(self): for record in self: db.exp_drop(self.name) self.write({'state': 'deleted'}) @api.multi def rename_database(self, new_dbname): for record in self: db.exp_rename(self.name, new_dbname) self.name = new_dbname @api.model def _transport_backup(self, dump_db, filename=None): ''' backup transport agents should override this ''' raise exceptions.Warning( _('''Transport agent has not been configured. You need either install one of saas_server_backup_* or remove saas_portal_backup''')) @api.multi def backup_database(self): res = [] for database_obj in self: data = {} data['name'] = database_obj.name filename = "%(db_name)s_%(timestamp)s.zip" % { 'db_name': database_obj.name, 'timestamp': datetime.utcnow().strftime("%Y-%m-%d_%H-%M-%SZ") } def dump_db(stream): return db.dump_db(database_obj.name, stream) try: database_obj._transport_backup(dump_db, filename=filename) data['status'] = 'success' except Exception as e: _logger.exception( 'An error happened during database %s backup' % (database_obj.name)) data['status'] = 'fail' data['message'] = str(e) res.append(data) return res @api.model def restart_server(self): odoo.service.server.restart() return True
class PosOrderLineCanceled(models.Model): _name = "pos.order.line.canceled" _description = "Order Line Canceled" _rec_name = "product_id" def _order_cancel_line_fields(self, line): if line and 'tax_ids' not in line[2]: product = self.env['product.product'].browse(line[2]['product_id']) line[2]['tax_ids'] = [(6, 0, [x.id for x in product.taxes_id])] return line product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], required=True, change_default=True, readonly=True) discount = fields.Float(string='Discount (%)', digits=0, default=0.0, readonly=True) price_unit = fields.Float(string='Unit Price', digits=0, readonly=True) user_id = fields.Many2one(comodel_name='res.users', string='Salesman', help="Person who removed order line", default=lambda self: self.env.uid, readonly=True) qty = fields.Float('Cancelled Quantity', default=1, readonly=True) current_qty = fields.Float('Remainder', default=0, readonly=True) reason = fields.Text(string="Reasons as Text", help="The Reason of Line Canceled", readonly=True) order_id = fields.Many2one('pos.order', string='Order Ref', ondelete='cascade', readonly=True) pack_lot_ids = fields.One2many('pos.pack.operation.lot', 'pos_order_line_id', string='Lot/serial Number', readonly=True) tax_ids = fields.Many2many('account.tax', string='Taxes', readonly=True) canceled_date = fields.Datetime(string='Cancelation Time', readonly=True, default=fields.Datetime.now) price_subtotal = fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal w/o Tax', store=True) price_subtotal_incl = fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal', store=True) cancelled_reason_ids = fields.Many2many('pos.cancelled_reason', 'reason_cancelled_lines_rel', 'canceled_line_id', 'cancelled_reason_id', string='Predefined Reasons') # the absolute_discount field is needed for compatibility # with <https://www.odoo.com/apps/modules/10.0/pos_orderline_absolute_discount/> module absolute_discount = fields.Float(string='Discount (abs)', digits=0, default=0.0, readonly=True) @api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id') def _compute_amount_line_all(self): for line in self: currency = line.order_id.pricelist_id.currency_id taxes = line.tax_ids.filtered(lambda tax: tax.company_id.id == line.order_id.company_id.id) fiscal_position_id = line.order_id.fiscal_position_id if fiscal_position_id: taxes = fiscal_position_id.map_tax(taxes, line.product_id, line.order_id.partner_id) price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) line.price_subtotal = line.price_subtotal_incl = price * line.qty if taxes: taxes = taxes.compute_all(price, currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False) line.price_subtotal = taxes['total_excluded'] line.price_subtotal_incl = taxes['total_included'] line.price_subtotal = currency.round(line.price_subtotal) line.price_subtotal_incl = currency.round(line.price_subtotal_incl) @api.model def create(self, values): if values.get('canceled_date'): canceled_date = datetime.strptime(values.get('canceled_date'), "%d/%m/%Y %H:%M:%S") tz = pytz.timezone(self.env.user.tz) if self.env.user.tz else pytz.utc canceled_date = tz.localize(canceled_date) canceled_date = canceled_date.astimezone(pytz.utc) canceled_date = fields.Datetime.to_string(canceled_date) values['canceled_date'] = canceled_date if values.get('cancelled_reason_ids') and len(values.get('cancelled_reason_ids')) > 0: values['cancelled_reason_ids'] = [(4, id) for id in values.get('cancelled_reason_ids')] return super(PosOrderLineCanceled, self).create(values)
class BlogPost(models.Model): _name = "blog.post" _description = "Blog Post" _inherit = ['mail.thread', 'website.seo.metadata', 'website.published.mixin'] _order = 'id DESC' _mail_post_access = 'read' @api.multi def _compute_website_url(self): super(BlogPost, self)._compute_website_url() for blog_post in self: blog_post.website_url = "/blog/%s/post/%s" % (slug(blog_post.blog_id), slug(blog_post)) @api.multi def _compute_ranking(self): res = {} for blog_post in self: age = datetime.now() - fields.Datetime.from_string(blog_post.post_date) res[blog_post.id] = blog_post.visits * (0.5 + random.random()) / max(3, age.days) return res def _default_content(self): return ''' <section class="s_text_block"> <div class="container"> <div class="row"> <div class="col-md-12 mb16 mt16"> <p class="o_default_snippet_text">''' + _("Start writing here...") + '''</p> </div> </div> </div> </section> ''' name = fields.Char('Title', required=True, translate=True, default='') subtitle = fields.Char('Sub Title', translate=True) author_id = fields.Many2one('res.partner', 'Author', default=lambda self: self.env.user.partner_id) active = fields.Boolean('Active', default=True) cover_properties = fields.Text( 'Cover Properties', default='{"background-image": "none", "background-color": "oe_black", "opacity": "0.2", "resize_class": ""}') blog_id = fields.Many2one('blog.blog', 'Blog', required=True, ondelete='cascade') tag_ids = fields.Many2many('blog.tag', string='Tags') content = fields.Html('Content', default=_default_content, translate=html_translate, sanitize_attributes=False) teaser = fields.Text('Teaser', compute='_compute_teaser', inverse='_set_teaser') teaser_manual = fields.Text(string='Teaser Content') website_message_ids = fields.One2many( 'mail.message', 'res_id', domain=lambda self: [ '&', '&', ('model', '=', self._name), ('message_type', '=', 'comment'), ('path', '=', False) ], string='Website Messages', help="Website communication history", ) # creation / update stuff create_date = fields.Datetime('Created on', index=True, readonly=True) published_date = fields.Datetime('Published Date') post_date = fields.Datetime('Published date', compute='_compute_post_date', inverse='_set_post_date', store=True) create_uid = fields.Many2one('res.users', 'Created by', index=True, readonly=True) write_date = fields.Datetime('Last Modified on', index=True, readonly=True) write_uid = fields.Many2one('res.users', 'Last Contributor', index=True, readonly=True) author_avatar = fields.Binary(related='author_id.image_small', string="Avatar") visits = fields.Integer('No of Views') ranking = fields.Float(compute='_compute_ranking', string='Ranking') @api.multi @api.depends('content', 'teaser_manual') def _compute_teaser(self): for blog_post in self: if blog_post.teaser_manual: blog_post.teaser = blog_post.teaser_manual else: content = html2plaintext(blog_post.content).replace('\n', ' ') blog_post.teaser = ' '.join(filter(None, content.split(' '))[:50]) + '...' @api.multi def _set_teaser(self): for blog_post in self: blog_post.teaser_manual = blog_post.teaser @api.multi @api.depends('create_date', 'published_date') def _compute_post_date(self): for blog_post in self: if blog_post.published_date: blog_post.post_date = blog_post.published_date else: blog_post.post_date = blog_post.create_date @api.multi def _set_post_date(self): for blog_post in self: blog_post.published_date = blog_post.post_date def _check_for_publication(self, vals): if vals.get('website_published'): for post in self: post.blog_id.message_post_with_view( 'website_blog.blog_post_template_new_post', subject=post.name, values={'post': post}, subtype_id=self.env['ir.model.data'].sudo().xmlid_to_res_id('website_blog.mt_blog_blog_published')) return True return False @api.model def create(self, vals): post_id = super(BlogPost, self.with_context(mail_create_nolog=True)).create(vals) post_id._check_for_publication(vals) return post_id @api.multi def write(self, vals): self.ensure_one() if 'website_published' in vals and 'published_date' not in vals: if self.published_date <= fields.Datetime.now(): vals['published_date'] = vals['website_published'] and fields.Datetime.now() result = super(BlogPost, self).write(vals) self._check_for_publication(vals) return result @api.multi def get_access_action(self): """ Instead of the classic form view, redirect to the post on website directly if user is an employee or if the post is published. """ self.ensure_one() if self.env.user.share and not self.sudo().website_published: return super(BlogPost, self).get_access_action() return { 'type': 'ir.actions.act_url', 'url': '/blog/%s/post/%s' % (self.blog_id.id, self.id), 'target': 'self', 'res_id': self.id, } @api.multi def _notification_recipients(self, message, groups): groups = super(BlogPost, self)._notification_recipients(message, groups) for group_name, group_method, group_data in groups: group_data['has_button_access'] = True return groups @api.multi def message_get_message_notify_values(self, message, message_values): """ Override to avoid keeping all notified recipients of a comment. We avoid tracking needaction on post comments. Only emails should be sufficient. """ if message.message_type == 'comment': return { 'needaction_partner_ids': [], } return {}
class Don(models.Model): _name = 'crm.alima.don' _description = 'Dons' codeMedia = fields.Many2one('crm.alima.code.media', string='code media', ondelete='restrict') adhesion = fields.Selection(OUIOUNON, string="Adhesion") date = fields.Date(string="Date du don", required=True) forme_don = fields.Selection(FORME_DON_RECU, index=True, string='forme du don') nature_don = fields.Selection(NATURE_DON, string="Nature du don") liberalite = fields.Selection(LIBERALITE, string='Liberalite') moyen_paiment = fields.Selection(MOYEN_PAIEMENT, string="Moyen de paiement") mode_versement = fields.Selection(MODE_VERSEMENT, string="Mode de versement") commentaire = fields.Text(string="Commentaire") date_recep = fields.Date(string='Date de récéption du chèque') date_signature = fields.Date(string='Date de signature du chèque') date_remise = fields.Date(string='Date de remise du chèque') date_encais = fields.Date(string='Date d\'encaissement du chèque') montantEur = fields.Float(string="Montant du don en Euro") montantLettre = fields.Char(string="Montant en toutes lettres en euro", compute='convNombre2lettres', store=True) devis_origine = fields.Selection(DEVISE, string='Devis d\'origine') montant_dans_devise = fields.Float( string='montant dans la devise d\'origine', compute='tauxChange') taux_change = fields.Float(string='taux de change à la date du don', default=1) nom_banque = fields.Char(string='Non banque') numCheque = fields.Char(string='Numero cheque') remise_globale = fields.Boolean(string="remise globale") montant_remise_globale = fields.Float(string="montant remise globale") plateforme_paiement = fields.Selection(PLATEFORME, string='plateforme de paiement') parametreRF = fields.Selection(RF, string='RF') NumRecuFiscal = fields.Char(string="numero reçu fiscal") dateEdition = fields.Date(string='date édition RF') dateEnvoi = fields.Date(string='Date envoi RF') prelevement_en_cours = fields.Boolean(string='Prelevement en cours') commentaire_autre = fields.Text(string="Commentaire") donateur = fields.Many2one('crm.alima.donateur', string='donateur', required=True) montantEurSave = fields.Float(string='Montant du don en Euro sauvegarder') valide = fields.Boolean(dafault=False, string='Validation creation') id_intersa = fields.Char(string='ID Intersa') url_intersa = fields.Char(compute='_get_url_intersa', string="Url Intersa") datetime_import = fields.Datetime(readonly=True, string="Date Import") @api.multi def copy(self, default): self.ensure_one() self.donateur.nombreDons += 1 return super(Don, self).copy(default) @api.model @api.returns('self', lambda value: value.id) def create(self, vals): res = super(Don, self).create(vals) res.montantEurSave = res.montantEur rec = res.donateur if not rec.datePremierDon: rec.datePremierDon = res.date rec.montantPremierDon = res.montantEur rec.codemediaPremierDon = res.codeMedia rec.dateDernierDon = res.date rec.montantDernierDon = res.montantEur rec.codemediaDernierDon = res.codeMedia rec.cumulDonTotal = res.montantEur rec.nombreDons = 1 rec.don_moy = rec.cumulDonTotal rec.idpremierdon = res.id rec.iddernierdon = res.id else: if datetime.strptime(res.date, '%Y-%m-%d') > datetime.strptime( rec.dateDernierDon, '%Y-%m-%d'): rec.dateDernierDon = res.date rec.montantDernierDon = res.montantEur rec.codemediaDernierDon = res.codeMedia rec.cumulDonTotal = rec.cumulDonTotal + res.montantEur rec.nombreDons = rec.nombreDons + 1 rec.don_moy = rec.cumulDonTotal / rec.nombreDons rec.iddernierdon = res.id if datetime.strptime(res.date, '%Y-%m-%d') < datetime.strptime( rec.datePremierDon, '%Y-%m-%d'): rec.datePremierDon = res.date rec.montantPremierDon = res.montantEur rec.codemediaPremierDon = res.codeMedia rec.cumulDonTotal = rec.cumulDonTotal + res.montantEur rec.nombreDons = rec.nombreDons + 1 rec.don_moy = rec.cumulDonTotal / rec.nombreDons rec.idpremierdon = res.id if not rec.datePremierDonHPA and res.mode_versement != 'avec prelevement': rec.datePremierDonHPA = res.date rec.montantPremierDonHPA = res.montantEur rec.codemediaPremierDonHPA = res.codeMedia rec.dateDernierDonHPA = res.date rec.montantDernierDonHPA = res.montantEur rec.codemediaDernierDonHPA = res.codeMedia rec.cumulDonHPA = res.montantEur rec.nombreDonsHPA = 1 rec.don_moyHPA = rec.cumulDonHPA rec.idpremierdonHPA = res.id rec.iddernierdonsHPA = res.id elif res.mode_versement != 'avec prelevement': if datetime.strptime(res.date, '%Y-%m-%d') > datetime.strptime( rec.dateDernierDonHPA, '%Y-%m-%d'): rec.dateDernierDonHPA = res.date rec.montantDernierDonHPA = res.montantEur rec.codemediaDernierDonHPA = res.codeMedia rec.cumulDonHPA = rec.cumulDonHPA + res.montantEur rec.nombreDonsHPA = rec.nombreDonsHPA + 1 rec.don_moyHPA = rec.cumulDonHPA / rec.nombreDonsHPA rec.iddernierdonsHPA = res.id if datetime.strptime(res.date, '%Y-%m-%d') < datetime.strptime( rec.datePremierDonHPA, '%Y-%m-%d'): rec.datePremierDonHPA = res.date rec.montantPremierDonHPA = res.montantEur rec.codemediaPremierDonHPA = res.codeMedia rec.cumulDonHPA = rec.cumulDonHPA + res.montantEur rec.nombreDonsHPA = rec.nombreDonsHPA + 1 rec.don_moyHPA = rec.cumulDonHPA / rec.nombreDonsHPA rec.idpremierdonHPA = res.id if rec.nombreDons == 0: rec.freq_communication = 'Prospect' elif rec.nombreDons == 1: rec.freq_communication = 'Nouveau' if rec.nombreDons > 1: rec.freq_communication = 'Consolide' if rec.cumulDonTotal < 1000: rec.montant = 'Donor' elif rec.cumulDonTotal >= 1000 and rec.cumulDonTotal <= 5000: rec.montant = 'Middle donor' elif rec.cumulDonTotal > 5000 and rec.cumulDonTotal <= 50000: rec.montant = 'Global Leader' elif rec.cumulDonTotal > 50000: rec.montant = 'Investisseur Fondateur' return res @api.multi def unlink(self): my_array = array('i', []) for dons in self: test = bool() for idd in my_array: if idd == dons.donateur.id: test = bool(1) if not test: my_array.append(dons.donateur.id) rep = super(Don, self).unlink() for iddonateur in my_array: rec = self.env['crm.alima.donateur'].search([('id', '=', iddonateur)]) rec.datePremierDon = "" rec.montantPremierDon = 0 rec.codemediaPremierDon = "" rec.dateDernierDon = "" rec.montantDernierDon = 0 rec.codemediaDernierDon = "" rec.cumulDonTotal = 0 rec.nombreDons = 0 rec.don_moy = 0 rec.idpremierdon = 0 rec.iddernierdon = 0 rec.datePremierDonHPA = "" rec.montantPremierDonHPA = 0 rec.codemediaPremierDonHPA = "" rec.dateDernierDonHPA = "" rec.montantDernierDonHPA = 0 rec.codemediaDernierDonHPA = 0 rec.cumulDonHPA = 0 rec.nombreDonsHPA = 0 rec.don_moyHPA = 0 rec.idpremierdonHPA = 0 rec.iddernierdonsHPA = 0 for res in rec.dons: rec = res.donateur if not rec.datePremierDon: rec.datePremierDon = res.date rec.montantPremierDon = res.montantEur rec.codemediaPremierDon = res.codeMedia rec.dateDernierDon = res.date rec.montantDernierDon = res.montantEur rec.codemediaDernierDon = res.codeMedia rec.cumulDonTotal = res.montantEur rec.nombreDons = 1 rec.don_moy = rec.cumulDonTotal rec.idpremierdon = res.id rec.iddernierdon = res.id else: rec.dateDernierDon = res.date rec.montantDernierDon = res.montantEur rec.codemediaDernierDon = res.codeMedia rec.cumulDonTotal = rec.cumulDonTotal + res.montantEur rec.nombreDons = rec.nombreDons + 1 rec.don_moy = rec.cumulDonTotal / rec.nombreDons rec.iddernierdon = res.id if not rec.datePremierDonHPA and res.mode_versement != 'avec prelevement': rec.datePremierDonHPA = res.date rec.montantPremierDonHPA = res.montantEur rec.codemediaPremierDonHPA = res.codeMedia rec.dateDernierDonHPA = res.date rec.montantDernierDonHPA = res.montantEur rec.codemediaDernierDonHPA = res.codeMedia rec.cumulDonHPA = res.montantEur rec.nombreDonsHPA = 1 rec.don_moyHPA = rec.cumulDonHPA rec.idpremierdonHPA = res.id rec.iddernierdonsHPA = res.id elif res.mode_versement != 'avec prelevement': rec.dateDernierDonHPA = res.date rec.montantDernierDonHPA = res.montantEur rec.codemediaDernierDonHPA = res.codeMedia rec.cumulDonHPA = rec.cumulDonHPA + res.montantEur rec.nombreDonsHPA = rec.nombreDonsHPA + 1 rec.don_moyHPA = rec.cumulDonHPA / rec.nombreDonsHPA rec.iddernierdonsHPA = res.id if rec.nombreDons == 0: rec.freq_communication = 'Prospect' elif rec.nombreDons == 1: rec.freq_communication = 'Nouveau' if rec.nombreDons > 1: rec.freq_communication = 'Consolide' if rec.cumulDonTotal < 1000: rec.montant = 'Donor' elif rec.cumulDonTotal >= 1000 and rec.cumulDonTotal <= 5000: rec.montant = 'Middle donor' elif rec.cumulDonTotal > 5000 and rec.cumulDonTotal <= 50000: rec.montant = 'Global Leader' elif rec.cumulDonTotal > 50000: rec.montant = 'Investisseur Fondateur' return rep @api.multi def write(self, vals): montantEurSave = self.montantEur rep = super(Don, self).write(vals) res = self rec = res.donateur if str(rec.idpremierdon) == str(res.id): rec.datePremierDon = res.date rec.montantPremierDon = res.montantEur rec.codemediaPremierDon = res.codeMedia if str(rec.iddernierdon) == str(res.id): rec.dateDernierDon = res.date rec.montantDernierDon = res.montantEur rec.codemediaDernierDon = res.codeMedia if str(rec.idpremierdon) == str(res.id) or str( rec.iddernierdon) == str(res.id): rec.cumulDonTotal = rec.cumulDonTotal - montantEurSave + res.montantEur rec.don_moy = rec.cumulDonTotal / rec.nombreDons if str(rec.idpremierdonHPA) == str(res.id): rec.datePremierDonHPA = res.date rec.montantPremierDonHPA = res.montantEur rec.codemediaPremierDonHPA = res.codeMedia if str(rec.iddernierdonsHPA) == str(res.id): rec.dateDernierDonHPA = res.date rec.montantDernierDonHPA = res.montantEur rec.codemediaDernierDonHPA = res.codeMedia if str(rec.idpremierdonHPA) == str(res.id) or str( rec.iddernierdonsHPA) == str(res.id): rec.cumulDonHPA = rec.cumulDonHPA - montantEurSave + res.montantEur rec.don_moyHPA = rec.cumulDonHPA / rec.nombreDonsHPA if rec.cumulDonTotal < 1000: rec.montant = 'Donor' elif rec.cumulDonTotal >= 1000 and rec.cumulDonTotal <= 5000: rec.montant = 'Middle donor' elif rec.cumulDonTotal > 5000 and rec.cumulDonTotal <= 50000: rec.montant = 'Global Leader' elif rec.cumulDonTotal > 50000: rec.montant = 'Investisseur Fondateur' return rep @api.depends('montantEur') def convNombre2lettres(self): for rec in self: nombre = rec.montantEur rec.montantLettre = rec.convNombre(int(nombre)) @api.depends('taux_change', 'montantEur') def tauxChange(self): for rec in self: rec.montant_dans_devise = rec.taux_change * rec.montantEur @api.depends('id_intersa') def _get_url_intersa(self): for rec in self: if rec.id_intersa: rec.url_intersa = 'https://www.intersa.fr/imagesAlima/INT_' + rec.id_intersa def convNombre(self, nombre): s = '' reste = nombre i = 1000000000 while i > 0: y = reste / i if y != 0: centaine = y / 100 dizaine = (y - centaine * 100) / 10 unite = y - centaine * 100 - dizaine * 10 if centaine == 1: s += "CENT " elif centaine != 0: s += schu[centaine] + "CENT " if dizaine == 0 and unite == 0: s = s[:-1] + "S " if dizaine not in [0, 1]: s += schd[dizaine] if unite == 0: if dizaine in [1, 7, 9]: s += "DIX " elif dizaine == 8: s = s[:-1] + "S " elif unite == 1: if dizaine in [1, 9]: s += "ONZE " elif dizaine == 7: s += "ET ONZE " elif dizaine in [2, 3, 4, 5, 6]: s += "ET UN " elif dizaine in [0, 8]: s += "UN " elif unite in [2, 3, 4, 5, 6, 7, 8, 9]: if dizaine in [1, 7, 9]: s += schud[unite] else: s += schu[unite] if i == 1000000000: if y > 1: s += "MILLIARDS " else: s += "MILLIARD " if i == 1000000: if y > 1: s += "MILLIONS " else: s += "MILLIONS " if i == 1000: s += "MILLE " if i == 1000 and s == 'UN MILLE ': s = "MILLE " #end if y!=0 reste -= y * i dix = False i /= 1000 #end while if len(s) == 0: s += "ZERO " return s @api.multi def reset_reconsiliation_don(self): all_don = self.env['crm.alima.don'].search([]) for don in all_don: don.write({'NumRecuFiscal': False}) @api.multi def correction_historique_don(self): id_donateur_a_traiter = [ 5202, 247, 8508, 3958, 1069, 3632, 5559, 2155, 3736, 6975, 4731, 4074, 1467, 4097, 3222, 2253, 1394, 4220, 6855, 3669, 273, 5213, 5420, 4926, 6139, 413, 701, 378, 2252, 4687, 465, 4553, 2594, 503, 3838, 3969, 1336, 4085, 594, 5029, 5422, ] donateur_a_traiter = self.env['crm.alima.donateur'].browse( id_donateur_a_traiter) for donateur in donateur_a_traiter: for don in donateur.dons: if datetime.strptime(don.date, '%Y-%m-%d') > datetime.strptime( donateur.dateDernierDon, '%Y-%m-%d'): donateur.dateDernierDon = don.date donateur.montantDernierDon = don.montantEur donateur.codemediaDernierDon = don.codeMedia donateur.iddernierdon = don.id if datetime.strptime(don.date, '%Y-%m-%d') < datetime.strptime( donateur.datePremierDon, '%Y-%m-%d'): donateur.datePremierDon = don.date donateur.montantPremierDon = don.montantEur donateur.codemediaPremierDon = don.codeMedia donateur.idpremierdon = don.id if don.mode_versement != 'avec prelevement': if datetime.strptime( don.date, '%Y-%m-%d') > datetime.strptime( donateur.dateDernierDonHPA, '%Y-%m-%d'): donateur.dateDernierDonHPA = don.date donateur.montantDernierDonHPA = don.montantEur donateur.codemediaDernierDonHPA = don.codeMedia donateur.iddernierdonsHPA = don.id if datetime.strptime( don.date, '%Y-%m-%d') < datetime.strptime( donateur.datePremierDonHPA, '%Y-%m-%d'): donateur.datePremierDonHPA = don.date donateur.montantPremierDonHPA = don.montantEur donateur.codemediaPremierDonHPA = don.codeMedia donateur.idpremierdonHPA = don.id
class reparacion(models.Model): _name = 'upotussam.reparacion' descripcion = fields.Char('Descripcion', size=64, required=True) fechaInicio = fields.Datetime('Fecha de Inicio', required=True, autodate=True) fechaFin = fields.Datetime('Fecha de Fin', required=True, autodate=True) mensajeCancelacion = fields.Char('Mensaje de Cancelacion', size=64, required=False) autobus_id = fields.Many2one('upotussam.autobus', 'Autobus', required=True) piezas_ids = fields.Many2many('upotussam.piezas', string='Piezas') mecanico_ids = fields.Many2many('upotussam.mecanico', string='Mecanicos involucrados') state = fields.Selection([ ('espera', 'En espera'), ('enviado', 'Autobus enviado'), ('reparacion', 'En reparacion'), ('finalizado', 'Finalizado'), ('cancelado', 'Cancelado'), ], 'Estado', default='espera') #botones @api.one def btn_submit_to_enviado(self): self.write({'state': 'enviado'}) @api.one def btn_submit_to_reparacion(self): if not self.mecanico_ids: raise models.ValidationError( 'Se tiene que asignar un mecanico minimo') else: self.write({'state': 'reparacion'}) @api.one def btn_submit_to_finalizado(self): self.write({'state': 'finalizado'}) @api.one def btn_submit_to_cancelado(self): self.write({'state': 'cancelado'}) #onchanges @api.onchange('piezas_ids') def onchange_piezas(self): if self.state != 'enviado': raise models.ValidationError( 'Solo se pueden asignar las piezas cuando se haya enviado el autobus a reparar' ) @api.onchange('mecanico_ids') def onchange_mecanico(self): if self.state != 'enviado': raise models.ValidationError( 'Solo se pueden asignar los mecanicos cuando se haya enviado el autobus a reparar' ) @api.onchange('descripcion', 'fechaInicio', 'fechaFin', 'garantia', 'autobus_id', 'piezas_ids', 'mecanico_ids') def onechange_cancelar(self): if self.state == 'cancelado': raise models.ValidationError( 'Una vez cancelado no se puede modificar') @api.onchange('mensajeCancelacion') def onchange_mecanico(self): if self.state != 'cancelado': raise models.ValidationError( 'Solo se puede añadir un mensaje si la reparacion se ha cancelado' ) @api.multi @api.onchange('mecanico_ids') def onchange_mecanicos_taller(self): taller = 0 for mecanico in self.mecanico_ids: if not taller: taller = mecanico.taller_id else: if mecanico.taller_id != taller: raise models.ValidationError( 'Tiene que ser el mismo taller')
class VendorDelayReport(models.Model): _name = "vendor.delay.report" _description = "Vendor Delay Report" _auto = False partner_id = fields.Many2one('res.partner', 'Vendor', readonly=True) product_id = fields.Many2one('product.product', 'Product', readonly=True) category_id = fields.Many2one('product.category', 'Product Category', readonly=True) date = fields.Datetime('Effective Date', readonly=True) qty_total = fields.Float('Total Quantity', readonly=True) qty_on_time = fields.Float('On-Time Quantity', readonly=True) on_time_rate = fields.Float('On-Time Delivery Rate', readonly=True) def init(self): tools.drop_view_if_exists(self.env.cr, 'vendor_delay_report') self.env.cr.execute(""" CREATE OR replace VIEW vendor_delay_report AS( SELECT m.id AS id, m.date AS date, m.purchase_line_id AS purchase_line_id, m.product_id AS product_id, Min(pc.id) AS category_id, Min(po.partner_id) AS partner_id, Sum(pol.product_uom_qty) AS qty_total, Sum(CASE WHEN pol.date_planned >= m.date THEN ml.qty_done ELSE 0 END) AS qty_on_time FROM stock_move m JOIN stock_move_line ml ON m.id = ml.move_id JOIN purchase_order_line pol ON pol.id = m.purchase_line_id JOIN purchase_order po ON po.id = pol.order_id JOIN product_product p ON p.id = m.product_id JOIN product_template pt ON pt.id = p.product_tmpl_id JOIN product_category pc ON pc.id = pt.categ_id WHERE m.state = 'done' GROUP BY m.id )""") @api.model def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): if 'on_time_rate' not in fields: res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy) return res fields.remove('on_time_rate') if 'qty_total' not in fields: fields.append('qty_total') if 'qty_on_time' not in fields: fields.append('qty_on_time') res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy) for group in res: if group['qty_total'] == 0: on_time_rate = 100 else: on_time_rate = group['qty_on_time'] / group['qty_total'] * 100 group.update({'on_time_rate': on_time_rate}) return res
class Customer(models.Model): _name = 'customer' name = fields.Char(string=u'公司名称') address = fields.Char(string=u'地址') website = fields.Char(string=u'网址') develop_time = fields.Datetime(string=u'开发时间', default=fields.Datetime.now) last_contact_time = fields.Datetime(string=u'上次联系时间', default=fields.Datetime.now) note = fields.Text(string=u'备注') active = fields.Boolean(string=u'可用', default=True) so_qty = fields.Float(string=u'销售订单数', store=False, compute='_get_so_qty') del_days = fields.Integer(string=u'间隔天数', related='grade_id.del_time', store=False) # del_days_final = fields.Integer(string=u'最终的间隔天数', store=False) origin_id = fields.Many2one('customer.origin', string=u'来源') grade_id = fields.Many2one('customer.grade', string=u'客户等级') category_id = fields.Many2one('customer.category', string=u'客户类型') develop_person = fields.Many2one('res.users', string=u'业务员', default=lambda self: self.env.user.id) country_id = fields.Many2one('country', string=u'国家') customer_state = fields.Many2one('customer.state', string=u'状态') sale_orders = fields.One2many('sale.order', 'customer_id', string=u'销售订单') so_remind_ids = fields.One2many('sale.order.remind', 'customer_id', string=u'交货日期提醒') # product_ids = fields.Many2many('product','customer_product_rel','customer_id','product_id',string=u'意向产品') contact_ids = fields.Many2many('customer.contact.person', 'customers_contacts_rel', 'customer_id', 'contact_id', string=u'联系人') _sql_constraints = [ ('CompanyName_unique', 'unique (name)', u'系统中已存在该公司!'), ('website_unique', 'unique (website)', u'系统中已存在该公司网址!'), ] #获取销售订单数 @api.one def _get_so_qty(self): for record in self: record.so_qty = len(record.sale_orders) #跳转到销售订单tree视图 @api.multi def view_sale_order(self): return { 'name': _(u'销售订单'), 'type': 'ir.actions.act_window', 'res_model': 'sale.order', 'view_type': 'form', 'view_mode': 'tree,form', 'domain': [('customer_id', '=', self.id)], } #customer contacter One2many的关系 @api.model def create(self, vals): contact_ids = vals.get('contact_ids') if contact_ids and len(contact_ids) >= 1: for item in contact_ids: if len(item) == 3: c_t_ps = self.env['customer.contact.person'].browse( item[2]) for c_t_p in c_t_ps: if c_t_p.customer_ids: info = u"【%s】已经是【%s】的联系人!" % ( c_t_p.name, c_t_p.customer_ids.name) raise ValidationError(_(info)) return super(Customer, self).create(vals) # customer contacter One2many的关系 @api.multi def write(self, vals): contact_ids = vals.get('contact_ids') # print self.id,self.ids if contact_ids and len(contact_ids) >= 1: for item in contact_ids: if len(item) == 3: c_t_ps = self.env['customer.contact.person'].browse( item[2]) for c_t_p in c_t_ps: # print c_t_p.customer_ids.id if c_t_p.customer_ids and c_t_p.customer_ids.id != self.id: info = u"【%s】已经是【%s】的联系人!" % ( c_t_p.name, c_t_p.customer_ids.name) raise ValidationError(_(info)) #update last_contact_time # last_contact_time = vals.get('last_contact_time') # if last_contact_time: # records = self.env['contact.customer'].search([('state','=','to_do'),('customer_id','=',self.id)]) # if records: # if len(records) >= 2: # info = u'有多条联系该客户的记录' # raise ValidationError(_(info)) # elif len(records) == 1: # last_contact_time = datetime.datetime.strptime(last_contact_time, '%Y-%m-%d %H:%M:%S') # if last_contact_time + datetime.timedelta(days=self.del_days) > datetime.datetime.now(): # records[0].state = 'done' return super(Customer, self).write(vals) #查看客户跟踪记录 def view_customer_follow_record(self): # print self.id return { 'name': _(u'客户跟踪记录'), 'type': 'ir.actions.act_window', 'res_model': 'customer.follow.record', 'view_type': 'form', 'view_mode': 'tree,form', 'domain': [('customer_id', '=', self.id)], }
class crm_claim(models.Model): _name = "crm.claim" _description = "Claim" _order = "priority,date desc" _inherit = ['mail.thread'] def _get_default_stage_id(self): """ Gives default stage_id """ team_id = self.env['crm.team'].sudo()._get_default_team_id() return self._stage_find(team_id=team_id.id, domain=[('sequence', '=', '1')]) @api.model def _default_user(self): return self.env.context.get('user_id', self.env.user.id) id = fields.Integer('ID', readonly=True) name = fields.Char('Claim Subject', required=True) active = fields.Boolean('Active',default=lambda *a: 1) action_next = fields.Char('Next Action') date_action_next = fields.Datetime('Next Action Date') description = fields.Text('Description') resolution = fields.Text('Resolution') create_date = fields.Datetime('Creation Date' , readonly=True) write_date = fields.Datetime('Update Date' , readonly=True) date_deadline = fields.Datetime('Deadline') date_closed = fields.Datetime('Closed', readonly=True) date = fields.Datetime('Claim Date', select=True,default=lambda self: self._context.get('date', fields.Datetime.now())) categ_id = fields.Many2one('crm.claim.category', 'Category') priority = fields.Selection([('0','Low'), ('1','Normal'), ('2','High')], 'Priority',default='1') type_action = fields.Selection([('correction','Corrective Action'),('prevention','Preventive Action')], 'Action Type') user_id = fields.Many2one('res.users', 'Responsible', track_visibility='always', default=_default_user) user_fault = fields.Char('Trouble Responsible') team_id = fields.Many2one('crm.team', 'Sales Team', oldname='section_id',\ select=True, help="Responsible sales team."\ " Define Responsible user and Email account for"\ " mail gateway.")#,default=lambda self: self.env['crm.team']._get_default_team_id() company_id = fields.Many2one('res.company', 'Company',default=lambda self: self.env['res.company']._company_default_get('crm.case')) partner_id = fields.Many2one('res.partner', 'Partner') 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") email_from = fields.Char('Email', size=128, help="Destination email for email gateway.") partner_phone = fields.Char('Phone') stage_id = fields.Many2one ('crm.claim.stage', 'Stage', track_visibility='onchange', domain="['|', ('team_ids', '=', team_id), ('case_default', '=', True)]") #,default=lambda self:self.env['crm.claim']._get_default_stage_id() cause = fields.Text('Root Cause') @api.onchange('partner_id') def onchange_partner_id(self, email=False): if not self.partner_id: return {'value': {'email_from': False, 'partner_phone': False}} address = self.pool.get('res.partner').browse(self.partner_id) return {'value': {'email_from': address.email, 'partner_phone': address.phone}} @api.model def create(self, vals): context = dict(self._context or {}) if vals.get('team_id') and not self._context.get('default_team_id'): context['default_team_id'] = vals.get('team_id') # context: no_log, because subtype already handle this return super(crm_claim, self).create(vals) def message_new(self,msg, custom_values=None): if custom_values is None: custom_values = {} desc = html2plaintext(msg.get('body')) if msg.get('body') else '' defaults = { 'name': msg.get('subject') or _("No Subject"), 'description': desc, 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), 'partner_id': msg.get('author_id', False), } if msg.get('priority'): defaults['priority'] = msg.get('priority') defaults.update(custom_values) return super(crm_claim, self).message_new(msg, custom_values=defaults)
class TierReview(models.Model): _name = "tier.review" _description = "Tier Review" name = fields.Char(related="definition_id.name", readonly=True) status = fields.Selection( selection=[ ("pending", "Pending"), ("rejected", "Rejected"), ("approved", "Approved"), ], default="pending", ) model = fields.Char(string="Related Document Model", index=True) res_id = fields.Integer(string="Related Document ID", index=True) definition_id = fields.Many2one(comodel_name="tier.definition") company_id = fields.Many2one( related="definition_id.company_id", store=True, ) review_type = fields.Selection(related="definition_id.review_type", readonly=True) reviewer_id = fields.Many2one(related="definition_id.reviewer_id", readonly=True) reviewer_group_id = fields.Many2one( related="definition_id.reviewer_group_id", readonly=True) reviewer_ids = fields.Many2many( string="Reviewers", comodel_name="res.users", compute="_compute_reviewer_ids", store=True, ) sequence = fields.Integer(string="Tier") todo_by = fields.Char(compute="_compute_todo_by", store=True) done_by = fields.Many2one(comodel_name="res.users") requested_by = fields.Many2one(comodel_name="res.users") reviewed_date = fields.Datetime(string="Validation Date") has_comment = fields.Boolean(related="definition_id.has_comment", readonly=True) comment = fields.Char(string="Comments") can_review = fields.Boolean( compute="_compute_can_review", store=True, help="""Can review will be marked if the review is pending and the approve sequence has been achieved""", ) approve_sequence = fields.Boolean(related="definition_id.approve_sequence", readonly=True) @api.depends("definition_id.approve_sequence") def _compute_can_review(self): for record in self: record.can_review = record._can_review_value() def _can_review_value(self): if self.status != "pending": return False if not self.approve_sequence: return True resource = self.env[self.model].browse(self.res_id) reviews = resource.review_ids.filtered(lambda r: r.status == "pending") if not reviews: return True sequence = min(reviews.mapped("sequence")) return self.sequence == sequence @api.model def _get_reviewer_fields(self): return ["reviewer_id", "reviewer_group_id", "reviewer_group_id.users"] @api.depends(lambda self: self._get_reviewer_fields()) def _compute_reviewer_ids(self): for rec in self: rec.reviewer_ids = rec._get_reviewers() @api.depends("reviewer_ids") def _compute_todo_by(self): """ Show by group or by abbrev list of names """ num_show = 3 # Max number of users to display for rec in self: todo_by = False if rec.reviewer_group_id: todo_by = _("Group %s") % rec.reviewer_group_id.name else: todo_by = ", ".join( rec.reviewer_ids[:num_show].mapped("display_name")) num_users = len(rec.reviewer_ids) if num_users > num_show: todo_by = "{} (and {} more)".format( todo_by, num_users - num_show) rec.todo_by = todo_by def _get_reviewers(self): return self.reviewer_id + self.reviewer_group_id.users
class PurchaseOrderLine(models.Model): _name = 'purchase.order.line' _description = 'Purchase Order Line' _order = 'order_id, sequence, id' name = fields.Text(string='Description', required=True) sequence = fields.Integer(string='Sequence', default=10) product_qty = fields.Float(string='Quantity', digits='Product Unit of Measure', required=True) product_uom_qty = fields.Float(string='Total Quantity', compute='_compute_product_uom_qty', store=True) date_planned = fields.Datetime(string='Scheduled Date', index=True) taxes_id = fields.Many2many( 'account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)]) product_uom = fields.Many2one( 'uom.uom', string='Unit of Measure', domain="[('category_id', '=', product_uom_category_id)]") product_uom_category_id = fields.Many2one( related='product_id.uom_id.category_id') product_id = fields.Many2one('product.product', string='Product', domain=[('purchase_ok', '=', True)], change_default=True) product_type = fields.Selection(related='product_id.type', readonly=True) price_unit = fields.Float(string='Unit Price', required=True, digits='Product Price') price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', store=True) price_total = fields.Monetary(compute='_compute_amount', string='Total', store=True) price_tax = fields.Float(compute='_compute_amount', string='Tax', store=True) order_id = fields.Many2one('purchase.order', string='Order Reference', index=True, required=True, ondelete='cascade') account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account') analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags') company_id = fields.Many2one('res.company', related='order_id.company_id', string='Company', store=True, readonly=True) state = fields.Selection(related='order_id.state', store=True, readonly=False) invoice_lines = fields.One2many('account.move.line', 'purchase_line_id', string="Bill Lines", readonly=True, copy=False) # Replace by invoiced Qty qty_invoiced = fields.Float(compute='_compute_qty_invoiced', string="Billed Qty", digits='Product Unit of Measure', store=True) qty_received_method = fields.Selection( [('manual', 'Manual')], string="Received Qty Method", compute='_compute_qty_received_method', store=True, help= "According to product configuration, the recieved quantity can be automatically computed by mechanism :\n" " - Manual: the quantity is set manually on the line\n" " - Stock Moves: the quantity comes from confirmed pickings\n") qty_received = fields.Float("Received Qty", compute='_compute_qty_received', inverse='_inverse_qty_received', compute_sudo=True, store=True, digits='Product Unit of Measure') qty_received_manual = fields.Float("Manual Received Qty", digits='Product Unit of Measure', copy=False) partner_id = fields.Many2one('res.partner', related='order_id.partner_id', string='Partner', readonly=True, store=True) currency_id = fields.Many2one(related='order_id.currency_id', store=True, string='Currency', readonly=True) date_order = fields.Datetime(related='order_id.date_order', string='Order Date', readonly=True) display_type = fields.Selection([('line_section', "Section"), ('line_note', "Note")], default=False, help="Technical field for UX purpose.") _sql_constraints = [ ('accountable_required_fields', "CHECK(display_type IS NOT NULL OR (product_id IS NOT NULL AND product_uom IS NOT NULL AND date_planned IS NOT NULL))", "Missing required fields on accountable purchase order line."), ('non_accountable_null_fields', "CHECK(display_type IS NULL OR (product_id IS NULL AND price_unit = 0 AND product_uom_qty = 0 AND product_uom IS NULL AND date_planned is NULL))", "Forbidden values on non-accountable purchase order line"), ] @api.depends('product_qty', 'price_unit', 'taxes_id') def _compute_amount(self): for line in self: vals = line._prepare_compute_all_values() taxes = line.taxes_id.compute_all(vals['price_unit'], vals['currency_id'], vals['product_qty'], vals['product'], vals['partner']) line.update({ 'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])), 'price_total': taxes['total_included'], 'price_subtotal': taxes['total_excluded'], }) def _prepare_compute_all_values(self): # Hook method to returns the different argument values for the # compute_all method, due to the fact that discounts mechanism # is not implemented yet on the purchase orders. # This method should disappear as soon as this feature is # also introduced like in the sales module. self.ensure_one() return { 'price_unit': self.price_unit, 'currency_id': self.order_id.currency_id, 'product_qty': self.product_qty, 'product': self.product_id, 'partner': self.order_id.partner_id, } def _compute_tax_id(self): for line in self: fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.with_company( line.company_id).property_account_position_id # If company_id is set, always filter taxes by the company taxes = line.product_id.supplier_taxes_id.filtered( lambda r: not line.company_id or r.company_id == line. company_id) line.taxes_id = fpos.map_tax( taxes, line.product_id, line.order_id.partner_id) if fpos else taxes @api.depends('invoice_lines.move_id.state', 'invoice_lines.quantity') def _compute_qty_invoiced(self): for line in self: qty = 0.0 for inv_line in line.invoice_lines: if inv_line.move_id.state not in ['cancel']: if inv_line.move_id.type == 'in_invoice': qty += inv_line.product_uom_id._compute_quantity( inv_line.quantity, line.product_uom) elif inv_line.move_id.type == 'in_refund': qty -= inv_line.product_uom_id._compute_quantity( inv_line.quantity, line.product_uom) line.qty_invoiced = qty @api.depends('product_id') def _compute_qty_received_method(self): for line in self: if line.product_id and line.product_id.type in [ 'consu', 'service' ]: line.qty_received_method = 'manual' else: line.qty_received_method = False @api.depends('qty_received_method', 'qty_received_manual') def _compute_qty_received(self): for line in self: if line.qty_received_method == 'manual': line.qty_received = line.qty_received_manual or 0.0 else: line.qty_received = 0.0 @api.onchange('qty_received') def _inverse_qty_received(self): """ When writing on qty_received, if the value should be modify manually (`qty_received_method` = 'manual' only), then we put the value in `qty_received_manual`. Otherwise, `qty_received_manual` should be False since the received qty is automatically compute by other mecanisms. """ for line in self: if line.qty_received_method == 'manual': line.qty_received_manual = line.qty_received else: line.qty_received_manual = 0.0 @api.model def create(self, values): if values.get('display_type', self.default_get(['display_type'])['display_type']): values.update(product_id=False, price_unit=0, product_uom_qty=0, product_uom=False, date_planned=False) order_id = values.get('order_id') if 'date_planned' not in values: order = self.env['purchase.order'].browse(order_id) if order.date_planned: values['date_planned'] = order.date_planned line = super(PurchaseOrderLine, self).create(values) if line.order_id.state == 'purchase': msg = _("Extra line with %s ") % (line.product_id.display_name, ) line.order_id.message_post(body=msg) return line def write(self, values): if 'display_type' in values and self.filtered( lambda line: line.display_type != values.get('display_type')): raise UserError( "You cannot change the type of a purchase order line. Instead you should delete the current line and create a new line of the proper type." ) if 'product_qty' in values: for line in self: if line.order_id.state == 'purchase': line.order_id.message_post_with_view( 'purchase.track_po_line_template', values={ 'line': line, 'product_qty': values['product_qty'] }, subtype_id=self.env.ref('mail.mt_note').id) return super(PurchaseOrderLine, self).write(values) def unlink(self): for line in self: if line.order_id.state in ['purchase', 'done']: raise UserError( _('Cannot delete a purchase order line which is in state \'%s\'.' ) % (line.state, )) return super(PurchaseOrderLine, self).unlink() @api.model def _get_date_planned(self, seller, po=False): """Return the datetime value to use as Schedule Date (``date_planned``) for PO Lines that correspond to the given product.seller_ids, when ordered at `date_order_str`. :param Model seller: used to fetch the delivery delay (if no seller is provided, the delay is 0) :param Model po: purchase.order, necessary only if the PO line is not yet attached to a PO. :rtype: datetime :return: desired Schedule Date for the PO line """ date_order = po.date_order if po else self.order_id.date_order if date_order: return date_order + relativedelta( days=seller.delay if seller else 0) else: return datetime.today() + relativedelta( days=seller.delay if seller else 0) @api.onchange('product_id') def onchange_product_id(self): if not self.product_id: return # Reset date, price and quantity since _onchange_quantity will provide default values self.date_planned = datetime.today().strftime( DEFAULT_SERVER_DATETIME_FORMAT) self.price_unit = self.product_qty = 0.0 self._product_id_change() self._suggest_quantity() self._onchange_quantity() def _product_id_change(self): if not self.product_id: return self.product_uom = self.product_id.uom_po_id or self.product_id.uom_id product_lang = self.product_id.with_context( lang=self.partner_id.lang, partner_id=self.partner_id.id, company_id=self.company_id.id, ) self.name = self._get_product_purchase_description(product_lang) self._compute_tax_id() @api.onchange('product_id') def onchange_product_id_warning(self): if not self.product_id or not self.env.user.has_group( 'purchase.group_warning_purchase'): return warning = {} title = False message = False product_info = self.product_id if product_info.purchase_line_warn != 'no-message': title = _("Warning for %s") % product_info.name message = product_info.purchase_line_warn_msg warning['title'] = title warning['message'] = message if product_info.purchase_line_warn == 'block': self.product_id = False return {'warning': warning} return {} @api.onchange('product_qty', 'product_uom') def _onchange_quantity(self): if not self.product_id: return params = {'order_id': self.order_id} seller = self.product_id._select_seller( partner_id=self.partner_id, quantity=self.product_qty, date=self.order_id.date_order and self.order_id.date_order.date(), uom_id=self.product_uom, params=params) if seller or not self.date_planned: self.date_planned = self._get_date_planned(seller).strftime( DEFAULT_SERVER_DATETIME_FORMAT) if not seller: if self.product_id.seller_ids.filtered( lambda s: s.name.id == self.partner_id.id): self.price_unit = 0.0 return price_unit = self.env['account.tax']._fix_tax_included_price_company( seller.price, self.product_id.supplier_taxes_id, self.taxes_id, self.company_id) if seller else 0.0 if price_unit and seller and self.order_id.currency_id and seller.currency_id != self.order_id.currency_id: price_unit = seller.currency_id._convert( price_unit, self.order_id.currency_id, self.order_id.company_id, self.date_order or fields.Date.today()) if seller and self.product_uom and seller.product_uom != self.product_uom: price_unit = seller.product_uom._compute_price( price_unit, self.product_uom) self.price_unit = price_unit @api.depends('product_uom', 'product_qty', 'product_id.uom_id') def _compute_product_uom_qty(self): for line in self: if line.product_id and line.product_id.uom_id != line.product_uom: line.product_uom_qty = line.product_uom._compute_quantity( line.product_qty, line.product_id.uom_id) else: line.product_uom_qty = line.product_qty def _suggest_quantity(self): ''' Suggest a minimal quantity based on the seller ''' if not self.product_id: return seller_min_qty = self.product_id.seller_ids\ .filtered(lambda r: r.name == self.order_id.partner_id and (not r.product_id or r.product_id == self.product_id))\ .sorted(key=lambda r: r.min_qty) if seller_min_qty: self.product_qty = seller_min_qty[0].min_qty or 1.0 self.product_uom = seller_min_qty[0].product_uom else: self.product_qty = 1.0 def _get_product_purchase_description(self, product_lang): self.ensure_one() name = product_lang.display_name if product_lang.description_purchase: name += '\n' + product_lang.description_purchase return name def _prepare_account_move_line(self, move): self.ensure_one() if self.product_id.purchase_method == 'purchase': qty = self.product_qty - self.qty_invoiced else: qty = self.qty_received - self.qty_invoiced if float_compare( qty, 0.0, precision_rounding=self.product_uom.rounding) <= 0: qty = 0.0 if self.currency_id == move.company_id.currency_id: currency = False else: currency = move.currency_id return { 'name': '%s: %s' % (self.order_id.name, self.name), 'move_id': move.id, 'currency_id': currency and currency.id or False, 'purchase_line_id': self.id, 'date_maturity': move.invoice_date_due, 'product_uom_id': self.product_uom.id, 'product_id': self.product_id.id, 'price_unit': self.price_unit, 'quantity': qty, 'partner_id': move.partner_id.id, 'analytic_account_id': self.account_analytic_id.id, 'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)], 'tax_ids': [(6, 0, self.taxes_id.ids)], 'display_type': self.display_type, }