class StockMoveLine(models.Model):
    _inherit = "stock.move.line"

    product_version_id = fields.Many2one(comodel_name="product.version",
                                         related="move_id.product_version_id",
                                         store="True")

    real_in = fields.Float(string="Real In",
                           compute="_compute_move_in_out_qty",
                           store="True")
    real_out = fields.Float(string="Real Out",
                            compute="_compute_move_in_out_qty",
                            store="True")
    virtual_in = fields.Float(string="Virtual In",
                              compute="_compute_move_in_out_qty",
                              store="True")
    virtual_out = fields.Float(string="Virtual Out",
                               compute="_compute_move_in_out_qty",
                               store="True")

    api.depends("location_id", "location_dest_id", "product_qty")

    def _compute_move_in_out_qty(self):
        for move in self:
            move.real_in = 1
            move.real_out = 2
            move.virtual_in = 3
            move.virtual_out = 4
Exemple #2
0
class Phonebook(models.Model):
    _name = 'phone.book'
    _description = "Phone Book"

    name = fields.Char(string="Name", required=True)
    related_partner = fields.Many2one('res.partner', string="Related Partner")
    date_of_joining = fields.Date(string='Date of Joining')
    category_id = fields.Many2many('res.partner.category', string="Tags")
    city = fields.Char(string="City", required=True)
    street = fields.Char(string="Street", required=True)
    country_id = fields.Many2one('res.country',
                                 string="Country",
                                 required=True)
    address = fields.Char('Full Address', compute='_calculate_address')
    address_for_printing = fields.Char('Printing Address')

    def print_name(self):
        print("Name of Record: %s" % self.name)
        return True

    api.depends('country_id', 'city', 'street')

    def _calculate_address(self):
        full_address = self.country_id.name + ' ,' + self.city + ' ,' + self.street
        self.address = full_address

    @api.model
    @api.onchange('name')
    def return_full_address(self):
        if self.name and self.address:
            self.address_for_printing = 'customer: ' + self.name + ' ' + self.address

    @api.model
    def create(self, values):
        if 'name' in values:
            values['name'] = values['name'].upper()
            new_rec = super(Phonebook, self).create(values)
            return new_rec

    @api.multi
    def write(self, values, context=None):
        if 'name' in values:
            values['name'] = values['name'].upper()
            old_rec = super(Phonebook, self).write(values)
        else:
            old_rec = super(Phonebook, self).write(values)
        return True

    @api.multi
    def unlink(self):
        for rec in self:
            if rec.name == 'JOHN':
                raise NameError(
                    'El campo con el nombre de JOHN no tienes permitido borrarlo'
                )
class ResPartner(models.Model):
    _inherit = 'res.partner'
    _order = 'name'

    published_book_ids = fields.One2many('library.book',
                                         'publisher_id',
                                         string='Published Books')

    authored_book_ids = fields.Many2many(
        'library.book',
        string='Authored Books',
        relation='library_book_res_partner_rel')
    count_books = fields.Integer('Number of Authored Books',
                                 compute='_compute_count_books')

    api.depends('authored_book_ids')

    def _compute_count_books(self):
        for r in self:
            r.count_books = len(r.authored_book_ids)

    @api.multi
    def create_partner(self):
        today_str = fields.Date.context_today(self)

        val1 = {
            'name': 'Eric Idle',
            'email': '*****@*****.**',
            'date': today_str
        }

        val2 = {
            'name': 'John Clesse',
            'email': '*****@*****.**',
            'date': today_str
        }

        partner_val = {
            'name': 'Flying Circus',
            'email': '*****@*****.**',
            'date': today_str,
            'is_company': True,
            'child_ids': [
                (0, 0, val1),
                (0, 0, val2),
            ]
        }

        record = self.env['res.partner'].create(partner_val)
        return record
Exemple #4
0
class PhoneBook(models.Model):
    _name = 'phone.book'        # db name in psql : phone_book (dot turns to underscore in psql)
    _description = "Phone Book"

    # ORM side of things | Similar to peewee
    name = fields.Char(string="Name", required= True)
    related_partner = fields.Many2one(comodel_name='res.partner', string="Related Partner")
    date_of_joining = fields.Date(string="Date Of Joining")
    category_id = fields.Many2many(comodel_name= 'res.partner.category', string="Tags")
    city = fields.Char(string="City", required=True)
    street = fields.Char(string="Street", required=True)
    country_id = fields.Many2one(comodel_name='res.country', string="Country")
    address = fields.Char(string="Full Address", compute='_calculate_address')
    address_for_printing = fields.Char(string="Printing Address", compute='return_full_address')



    def print_name(self):
        print("Name of record: %s" %self.name)
        return True



    api.depends('country_id','city','street')
    def _calculate_address(self):
        # if country_id is None:
        #     full_address = self.city + ' ,' + self.street
        # else:
        full_address = self.country_id.name + ' ,' + self.city + ' ,' + self.street
        self.address = full_address



    @api.model
    @api.onchange('name','address')       # if change 'name' or 'address', everything will change automatically
    def return_full_address(self):
        if self.name and self.address:
            self.address_for_printing = 'customer: ' + self.name + ' ,' + self.address



# see oreilly section6 last chapter on explanation @api.one , self.ensure_one() , @api.model

    # using create() function to upper() case the 'name' enterred when we create() new form
    @api.model
    def create(self,values):
        if 'name' in values:
            values['name'] = values['name'].upper()
            new_rec = super(PhoneBook, self).create(values)
            return new_rec

    # write(), same as create(), but applies for editing. create() meant for create new file ONLY
    @api.multi
    def write(self, values, context = None):
        if 'name' in values:
            values['name'] = values['name'].upper()
            old_rec = super(PhoneBook, self).write(values)
        else:
            old_rec = super(PhoneBook, self).write(values)
        return True

    # unlink(), ensuring that you can/cant delete something if the criteria are satisfied
    @api.multi
    def unlink(self):
        for rec in self:
            if rec.name == 'JOHN':
                raise NameError('Name that is exactly "JOHN" cant be deleted...because f**k you, thats why lmao')
Exemple #5
0
    additional_landed_cost = fields.Float(
        'Additional Landed Cost',
        digits='Product Price')
    final_cost = fields.Float(
=======
    former_cost = fields.Monetary(
        'Original Value')
    additional_landed_cost = fields.Monetary(
        'Additional Landed Cost')
    final_cost = fields.Monetary(
>>>>>>> f0a66d05e70e432d35dc68c9fb1e1cc6e51b40b8
        'New Value', compute='_compute_final_cost',
        store=True)
    currency_id = fields.Many2one('res.currency', related='cost_id.company_id.currency_id')

    @api.depends('cost_line_id.name', 'product_id.code', 'product_id.name')
    def _compute_name(self):
        for line in self:
            name = '%s - ' % (line.cost_line_id.name if line.cost_line_id else '')
            line.name = name + (line.product_id.code or line.product_id.name or '')

    @api.depends('former_cost', 'additional_landed_cost')
    def _compute_final_cost(self):
        for line in self:
            line.final_cost = line.former_cost + line.additional_landed_cost

    def _create_accounting_entries(self, move, qty_out):
        # TDE CLEANME: product chosen for computation ?
        cost_product = self.cost_line_id.product_id
        if not cost_product:
            return False
Exemple #6
0
    def get_lab_warning_icon(self):
        if (self.warning):
            self.lab_warning_icon = 'medical-warning'

    @api.depends('result')
Exemple #7
0
    def _onchange_partner(self):
        if self.partner_id:
            self.delivery_id = self.partner_id  # MAJ d'un autre champ
            # OU
            vals = {'delivery_id': self.partner_id.id}
            self.update(vals)
            # M2M : 2 possibilités :
            # - liste d'IDs, mais ça va AJOUTER les ids, comme (4, [IDs])
            # - [(6, 0, [IDs])], ce qui va remplacer les ids
            # (cf module product_category_tax dans akretion/odoo-usability)
            # On utilise un autre M2M pour le mettre à jour, on peut faire
            # self.champ_M2M_ids.ids -> ça donne la liste des IDs
            # M2O : recordset (ou ID)
            # O2M : exemple v10 dans purchase/models/account_invoice.py
            # méthode purchase_order_change()
            # là, Odoo va jouer automatiquement le @api.onchange du champ delivery_id
            # pas besoin d'appeler le onchange de delivery_id dans notre code
        # Here, all form values are set on self
        # assigned values are not written to DB, but returned to the client
        # It is not possible to output a warning
        # It is not possible to put a raise UserError()
        # in this function (it will crash odoo)
        res = {'warning':
            {'title': _('Be careful'),
            {'message': _('here is the msg')}}
        # pour un domaine
        res = {'domain': {
            'champ1': "[('product_id', '=', product_id)]",
            'champ2': "[]"},
            }
        # si on a besoin de changer le contexte (astuce qui peut être utile
        # pour ne pas déclancher en cascade les autres api.onchange qui filtreraient
        # sur le contexte
        self.env.context = self.with_context(olive_onchange=True).env.context
        # astuce trouvée sur https://github.com/odoo/odoo/issues/7472
        return res
        # si je n'ai ni warning ni domain, je n'ai pas besoin de faire un return

    # La fonction de calcul du champ function price_subtotal
    @api.one  # auto-loop decorator
    @api.depends('price_unit', 'discount', 'invoice_line_tax_id', 'quantity',
        'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id')
    # @api.depends est utilisé pour: invalidation cache, recalcul, onchange
    # donc, maintenant, le fait d'avoir un champ calculé fait qu'il est
    # automatiquement mis à jour dans la vue quand un de ses champs 'depends'
    # est modifié ! COOOOOL !
    # ATTENTION : si chgt de @api.depends, faire -u module !
    # Pour un one2many : ne PAS juste indiquer le nom du champ o2m, sinon il ne fait rien
    # il faut aussi indiquer un champ sur le O2M. Exemple : 'line_ids.request_id'
    # Apparemment, on peut mettre dans @api.depends un champ fonction stocké et ça va bien
    # faire le recalcul en cascade
    # (ça n'a pas l'air de marcher qd on met un api.depends sur un champ non stocké)
    def _compute_price(self):
        price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
        taxes = self.invoice_line_tax_id.compute_all(price, self.quantity, product=self.product_id, partner=self.invoice_id.partner_id)
        self.price_subtotal = taxes['total']  # calcul et stockage de la valeur
        self.second_field = 'iuit'  # calcul et stockage d'un 2e champ
                                    # equivalent de multi='pouet'
        # Pour un champ O2M ou M2M, envoyer un recordset multiple ou une liste d'IDS
        # pour un champ M2O, donner le recordset ou l'ID
        if self.invoice_id:
            self.price_subtotal = self.invoice_id.currency_id.round(self.price_subtotal)
        # Pas besoin de return !
        # on ne peut PAS faire un self.write({}) dans la fonction de calcul d'un champ fonction

    # Pour un champ fonction, on peut aussi faire @api.multi:
    # untaxed = fields.Float(compute='_amounts')
    # taxes = fields.Float(compute='_amounts')
    # total = fields.Float(compute='_amounts')
    @api.multi
    @api.depends('lines.amount', 'lines.taxes')
    def _amounts(self):
        for order in self:
            order.untaxed = sum(line.amount for line in order.lines)
            order.taxes = sum(line.taxes for line in order.lines)
            order.total = order.untaxed + order + taxes

    # Champ fonction inverse='_inverse_price'
    @api.onchange('name')  # add @api.onchange on an inverse method to have it apply immediately and not upon save
    def _inverse_loud(self):
        for rec in self:
            rec.name = (rec.loud or '').lower()  # MAJ du ou des autres champs

    # Champ fonction search='_search_price'
    def _search_loud(self, operator, value):
        if value is not False:
            value = value.lower()
        today = fields.Date.context_today(self)
        self._cr.execute('SELECT id FROM [cur_obj] WHERE (fortress_type <> %s OR (fortress_type = %s AND effectivity_date is not null)) AND (end_date is null OR end_date > %s)', (today, ))
        res_ids = [x[0] for x in self._cr.fetchall()]
        res = [('id', 'in', res_ids)] # recherche sur les autres champs
        return res

    # Fonction default=_default_account
    @api.model
    def _default_account(self):
        return valeur_par_defaut
        # M2O : retourne un recordset ou un ID (ou False)
        # (NOTE: apparemment, en v8, il veut un ID)
        # OUTDATED (?) : ATTENTION, si on veut un M2O à False, il ne pas que la fonction
        #       _default_account retourne False mais self.env['..'].browse(False)
        # O2M : retourne une liste de dict contenant la valeur des champs
        # M2M : retourne un recordset multiple ?
        # date : string ou objet datetime

    # Fonction pour fields.selection
    @api.model
    def _type_list_get(self):
        return [('key1', _('String1')), ('key2', _('String2'))]

    ### CHAMPS
    # id, create_uid, write_uid, create_date et write_date
    # sont déjà utilisable dans le code python sans re-définition
    active = fields.Boolean(default=True)
    # Par défaut, string = nom du champ avec majuscule pour chaque début de mot
    login = fields.Char(
        string='Login', size=16, translate=True, required=True,
        help="My help message")
    display_name = fields.Char(
        string='Display Name', compute='_compute_display_name',
        readonly=True, store=True)
    comment = fields.Text(string='Comment', translate=True)
    html = fields.Html(string='report', translate=True)
    code_digits = fields.Integer(
        string='# of Digits', track_visibility='onchange', default=12,
        groups='base.group_user')
    # OU groups=['base.group_user', 'base.group_hr_manager']
    # groups = XMLID : restriction du read/write et invisible ds les vues ET EXPORT
    # v13: track_visibility='onchange' => tracking=X
    sequence = fields.Integer(default=10)
    # track_visibility = always ou onchange
    amount_untaxed = fields.Float(
        'Amount untaxed', digits='Product Unit of Measure',
        group_operator="avg")  # Utile pour un pourcentage par exemple
    # v13 : digits='Product Unit of Measure'
    # v12- : digits=dp.get_precision('Account')
    # digits=(precision, scale)   exemple (16, 2)
    # Scale est le nombre de chiffres après la virgule
    # quand le float est un fields.float ou un fields.function,
    # on met l'option : digits=dp.get_precision('Account')
    # Autres valeurs possibles pour get_precision : product/product_data.xml
    # Product Price, Discount, Stock Weight, Product Unit of Measure,
    # Product UoS (v8 only)
    # fields.Monetary is only in version >= 9.0
    debit = fields.Monetary(default=0.0, currency_field='company_currency_id')
    start_date = fields.Date(
        string='Start Date', copy=False, default=fields.Date.context_today,
        index=True)
    # similaire : fields.Datetime and fields.Time
    start_datetime = fields.Datetime(
        string='Start Date and Time', default=fields.Datetime.now)
    # index=True => the field will be indexed in the database
    # (much faster when you search on that field)
    type = fields.Selection([
        ('import', 'Import'),
        ('export', 'Export'),
        ], string="Type",
        default=lambda self: self._context.get('type', 'export'))
    # FIELDS.SELECTION ac selection dynamique :
    # type = fields.Selection('_type_list_get', string='Type', help='Pouet'),
    # Plus besoin de la double fonction pour que la 2e soit héritable
    # Pour ajouter des champs à un fields.Selection existant:
    # fields.Selection(
    #    selection_add=[('new_key1', 'My new key1'), ('new_key2', 'My New Key2')])
    # v14 : ondelete={"new_key1": "set default"}
    # other possible options for ondelete: set null, cascade (delete the records !)
    # Pour afficher la valeur 'lisible' du champ selection (v12+):
    # rec._fields['type'].convert_to_export(rec.type, rec)
    picture = fields.Binary(string='Picture', attachment=True)
    # Pour fields.binary, il existe une option filters='*.png, *.gif',
    # qui restreint les formats de fichiers sélectionnables dans
    # la boite de dialogue, mais ça ne marche pas en GTK (on
    # ne peut rien sélectionner) et c'est pas supporté en Web, cf
    # https://bugs.launchpad.net/openobject-server/+bug/1076895
    picture_filename = fields.Char(string='Filename')
    # Les champs "picture" et "picture_filename" sont liés ensemble dans la vue
    # via la balise filename="picture_filename" sur le champ 'picture'
    # Il faut que le champ 'picture_filename' soit présent dans la vue
    # (il peut être invisible)
    # Pour un fichier à télécharger d'Odoo, le nom du fichier aura la valeur de
    # picture_filename
    # Pour un fichier à uploader dans Odoo, 'picture_filename' vaudra le nom
    # du fichier uploadé par l'utilisateur

    # Exemple de champ fonction stocké
    price_subtotal = fields.Float(
        string='Amount', digits= dp.get_precision('Account'),
        store=True, readonly=True, compute='_compute_price')
    # Exemple de champ function non stocké avec fonction inverse
    loud = fields.Char(
        store=False, compute='_compute_loud', inverse='_inverse_loud',
        search='_search_loud')
    account_id = fields.Many2one('account.account', string='Account',
        required=True, domain=[('type', 'not in', ['view', 'closed'])],
        default=lambda self: self._default_account(),
        check_company=True)
        # L'utilisation de lambda permet d'hériter la fonction _default_account() sans
        # hériter le champ. Sinon, on peut aussi utiliser default=_default_account
        # Possibilité d'hériter un domaine:
        # domain=lambda self: [('reconcile', '=', True), ('user_type_id.id', '=', self.env.ref('account.data_account_type_current_assets').id), ('deprecated', '=', False)]
    company_id = fields.Many2one(
        'res.company', string='Company',
        ondelete='cascade', required=True,
        default=lambda self: self.env['res.company']._company_default_get()
        default=lambda self: self.env.company)  # v13
        # si on veut que tous les args soient nommés : comodel_name='res.company'
    user_id = fields.Many2one(
        'res.users', string='Salesman', default=lambda self: self.env.user)
    # ATTENTION : si j'ai déjà un domaine sur la vue,
    # c'est le domaine sur la vue qui prime !
    # ondelete='cascade' :
    # le fait de supprimer la company va supprimer l'objet courant !
    # ondelete='set null' (default)
    # si on supprime la company, le champ company_id est mis à 0
    # ondelete='restrict' :
    # si on supprime la company, ça déclanche une erreur d'intégrité !

    # Champ Relation
    company_currency_id = fields.Many2one(
        'res.currency', string='Currency', related='company_id.currency_id',
        store=True)  # option related_sudo=True by default
    # ATTENTION, en nouvelle API, on ne peut PAS faire un fields.Char qui
    # soit un related d'un fields.Selection (bloque le démarrage d'Odoo
    # sans message d'erreur !)

    line_ids = fields.One2many(
        'product.code.line', 'parent_id', string='Product lines',
        states={'done': [('readonly', True)]}, copy=True)
        # OU comodel_name='product.code.line', inverse_name='parent_id'
    # 2e arg = nom du champ sur l'objet destination qui est le M20 inverse
    # en v8 :
    # copy=True pour que les lignes soient copiées lors d'un duplicate
    # sinon, mettre copy=False (ça ne peut être qu'un booléen)
    # Valeur par défaut du paramètre "copy": True for normal fields, False for
    # one2many and computed fields, including property fields and related fields
    # ATTENTION : pour que states={} marche sur le champ A et que le
    # champ A est dans la vue tree, alors il faut que le champ "state"
    # soit aussi dans la vue tree.

    partner_ids = fields.Many2many(
        'res.partner', 'product_code_partner_rel', 'code_id', 'partner_id',
        'Related Partners')
    # 2e arg = nom de la table relation
    # 3e arg ou column1 = nom de la colonne dans la table relation
    # pour stocker l'ID du product.code
    # 4e arg ou column2 = nom de la colonne dans la table relation
    # pour stocker l'ID du res.partner
    # OU
    partner_ids = fields.Many2many(
        'res.partner', column1='code_id', column2='partner_id',
        string='Related Partners')
    # OU
    partner_ids = fields.Many2many(
        'res.partner', string='Related Partners')
    # Pour les 2 dernières définitions du M2M, il ne faut pas avoir
    # plusieurs champs M2M qui pointent du même obj vers le même obj

    # Champ property: il suffit de définit le champ comme un champ normal
    # et d'ajouter un argument company_dependent=True
    # Quand on veut lire la valeur d'un champ property dans une société
    # qui n'est pas celle de l'utilisateur, il faut passer dans le context
    # 'force_company': 8  (8 = ID de la company)
    }
Exemple #8
0
class ChangeProject(models.Model):
    _description = "Change Project"
    _name = 'change.project'
    _inherit = ['mail.activity.mixin', 'mail.thread']
    _order = 'id desc'

    @api.model
    def _needaction_domain_get(self):
        return [('state', '!=', 'resuelto')]

    name = fields.Char('Código', default="Nuevo", copy=False)
    title = fields.Char('Título', default="Reunión")
    obs = fields.Text('Observación')
    obs_solucion = fields.Text('Observación Solución')
    entry_date = fields.Datetime('Fecha de Entrada',
                                 default=fields.Datetime.now)
    end_date = fields.Datetime('Fecha de Salida')
    end_will_end = fields.Datetime('Fecha Prevista')
    user_id = fields.Many2one('res.users',
                              string='Creado',
                              default=lambda self: self.env.user)

    notas = fields.Char('Notas')

    category_id = fields.Many2one('ticket.category', string='Categoría')
    ticket_id = fields.Many2one('ticket.pro', string='Ticket Soporte')

    total_horas = fields.Float('Horas', compute='_total_price_sum')
    total_price = fields.Float('Precio Total', compute='_total_price_sum')

    api.depends('hours_ids')

    def _total_price_sum(self):
        suma = 0
        suma_horas = 0

        for move in self:
            for line in move.hours_ids:
                suma += line.total_price
                suma_horas += line.cant_horas

            move.total_price = suma
            move.total_horas = suma_horas

    user_error_id = fields.Many2one('res.users',
                                    string='Usuario',
                                    default=lambda self: self.env.user)

    comprobante_01_name = fields.Char("Adjunto")
    comprobante_01 = fields.Binary(string='Adjunto',
                                   copy=False,
                                   help='Adjunto')

    company_id = fields.Many2one(
        'res.company',
        string="Compañia",
        required=True,
        default=lambda self: self.env.user.company_id.id)

    state = fields.Selection([('borrador', 'Borrador'),
                              ('aprobado', 'Aprobado'),
                              ('trabajando', 'Trabajando'),
                              ('resuelto', 'Resuelto'),
                              ('calificado', 'Calificado')],
                             string='Estatus',
                             index=True,
                             readonly=True,
                             default='borrador',
                             copy=False)

    calificacion = fields.Selection([('0', 'Malo'), ('1', 'Regular'),
                                     ('2', 'Bueno'), ('3', 'Excelente')],
                                    string='Calificación',
                                    default='0',
                                    copy=False)

    obs_calificacion = fields.Text('Nota Calificación')

    prioridad = fields.Selection([('baja', 'Baja'), ('media', 'Media'),
                                  ('alta', 'Alta')],
                                 default='baja',
                                 copy=False)

    def exe_autorizar_2(self):
        for record in self:
            record.state = 'aprobado'
            record.message_post(body=_("Ticket Aprobado por: %s") %
                                record.env.user.name)

    def exe_work_2(self):
        for record in self:
            record.user_work_id = record.env.user
            record.state = 'trabajando'
            record.message_post(body=_("Iniciando el trabajo: %s") %
                                record.env.user.name)

    def exe_resuelto_2(self):
        for record in self:
            record.user_work_id = record.env.user
            record.state = 'resuelto'
            record.message_post(body=_("Nota Solución: %s") %
                                record.obs_solucion)
            record.end_date = fields.Datetime.now()

    def exe_abrir_2(self):
        for record in self:
            # record.numero_veces = record.numero_veces + 1
            record.state = 'borrador'
            record.message_post(body=_("Se Abre de nuevo: %s") %
                                record.env.user.name)

    def exe_close_2(self):
        if self.calificacion == '0':
            raise ValidationError(
                "Por favor califica nuestro trabajo así mejoramos con tu ayuda, muchas gracias."
            )
        for record in self:
            record.state = 'calificado'
            record.message_post(body=_("Calificado como: %s") %
                                record.calificacion)

    @api.model
    def create(self, vals):
        if vals.get('name', "Nuevo") == "Nuevo":
            vals['name'] = self.env['ir.sequence'].next_by_code(
                'change.project') or "Nuevo"
        ticket = super(ChangeProject, self).create(vals)
        template = self.env.ref('ticket_pro.email_change_project')
        if ticket.comprobante_01:
            attachment = self.env['ir.attachment'].create({
                'name':
                ticket.comprobante_01_name,
                'datas':
                ticket.comprobante_01,
                'datas_fname':
                ticket.comprobante_01_name,
                'res_model':
                'change.project',
                'type':
                'binary'
            })
            template.attachment_ids = [(6, 0, attachment.ids)]
        mail = template.send_mail(ticket.id, force_send=True)  # envia mail
        if mail:
            ticket.message_post(body=_("Enviado email al Cliente: %s" %
                                       ticket.category_id.name))
        return ticket

    hours_ids = fields.One2many('hours.task',
                                'cambios_id',
                                string='Listado Horas')
Exemple #9
0
class npa_expenses(models.Model):
    _name = 'npa.expense_details_hdr'
    _description = 'Apty Expense details table'
    _order = 'name'

    # Disable the DUPLICATE button
    def copy(self):
        raise ValidationError("Sorry you are unable to duplicate records")

    # Perform a soft delete
    def unlink(self):
        for rec in self:
            rec.active = False

    # Returns age of staff
    api.depends('to_date', 'from_date')

    def _compute_days(self):
        for rec in self:
            if rec.from_date and rec.to_date:
                dur = rec.to_date - rec.from_date
                rec.age = dur.days + 1

    # Method to payment wizard
    def create_service_payment(self):
        mod_obj = self.env['ir.model.data']
        form_res = mod_obj.get_object_reference('apty_acctmgmt',
                                                'view_shop_payment_form')[1]
        return {
            'name': "Create Payment",
            'view_type': 'form',
            'view_mode': "[form]",
            'view_id': form_res,
            'res_model': 'npa.shop_payment_amt',
            'type': 'ir.actions.act_window',
            'views': [(form_res, 'form')],
            'target': 'new',
            'context': {
                'default_service_id': self.id
            }
        }

    # Method to post purchase request
    def get_service_request_wizard(self):
        try:
            form_id = self.env['ir.model.data'].get_object_reference(
                'apty_acctmgmt', 'post_service_request_wizard')[1]
        except ValueError:
            form_id = False
            raise Warning(
                _("Cannot locate required 'post_service_request_wizard'. Please contact IT Support"
                  ))
        return {
            'name': "Post Service Transaction",
            'view_type': 'form',
            'view_mode': "[form]",
            'view_id': False,
            'res_model': 'npa.common_wizard',
            'type': 'ir.actions.act_window',
            'target': 'new',
            'views': [(form_id, 'form')],
        }

    @api.model
    def create(self, vals):
        if not vals:
            vals = {}
        vals['name'] = self.env['ir.sequence'].\
            next_by_code('npa.expense_details_hdr') or 'New'
        return super(npa_expenses, self).create(vals)

    # Calculate total transaction and total payment amount
    @api.depends('expense_ids.service_amt', 'payment_ids.payment_amt')
    def _compute_total_amt(self):
        total_amt = 0.0
        total_payment = 0.0
        for record in self:
            for line in record.expense_ids:
                total_amt += line.service_amt
            for line in record.payment_ids:
                total_payment += line.payment_amt
            print('****** payment total', total_payment)
            record.total_service_amt = total_amt
            record.payment_amt = total_payment
            record.amount_residual = total_amt - total_payment

    # field
    name = fields.Char(size=80,
                       string='Service Order',
                       readonly=True,
                       required=True,
                       copy=False,
                       default='New')
    service_name = fields.Char(size=80, string='Service Name')
    from_date = fields.Date(string="From date/time")
    to_date = fields.Date(string="To date/time")
    age = fields.Integer(string='No. Of Days', readonly=True, store=False)
    service_desc = fields.Text(string="Service Description")
    active = fields.Boolean(string='Active', default=True)
    expense_ids = fields.One2many(comodel_name='npa.expense_details_sumry',
                                  inverse_name='service_id',
                                  string='Services')
    payment_ids = fields.One2many(comodel_name='npa.shop_payment_amt',
                                  inverse_name='service_id',
                                  string='Payment Amount')
    date_posted = fields.Date(string='Posted Date')
    posted_by = fields.Many2one(comodel_name='res.users',
                                string='Posted by',
                                ondelete='restrict')
    total_service_amt = fields.Float(string='Total Amount',
                                     digits=(5, 2),
                                     compute='_compute_total_amt',
                                     store=True)
    payment_amt = fields.Float(string='Amount Paid',
                               digits=(5, 2),
                               compute='_compute_total_amt',
                               store=True)
    amount_residual = fields.Float(string='Amount Due',
                                   digits=(5, 2),
                                   compute='_compute_total_amt',
                                   store=True)
    state = fields.Selection([
        ('New', 'New'),
        ('Posted', 'Posted'),
        ('Cancel', 'Cancel'),
    ],
                             string='State',
                             default='New')
class Session(models.Model):
    _name = 'academico.session'
    _description = "Acadêmico Sessions"

    name = fields.Char(required=True)
    start_date = fields.Date(default=fields.Date.today)
    duration = fields.Float(digits=(6, 2), help="Duration in days")
    seats = fields.Integer(string="Number of seats")
    active = fields.Boolean(default=True)
    color = fields.Integer()

    
    instructor_id = fields.Many2one('res.partner', string="Instructor",
        domain=['|', ('instructor', '=', True),
                     ('category_id.name', 'ilike', "Teacher")])

    
    course_id = fields.Many2one('academico.course',
        ondelete='cascade', string="Course", required=True)
    attendee_ids = fields.Many2many('res.partner', string="Attendees")
    
    taken_seats = fields.Float(string="Taken seats", compute='_taken_seats')
    
    end_date = fields.Date(string="End Date", store=True,
        compute='_get_end_date', inverse='_set_end_date')
    
    attendees_count = fields.Integer(
        string="Attendees count", compute='_get_attendees_count', store=True)



    @api.depends('seats', 'attendee_ids')
    def _taken_seats(self):
        for r in self:
            if not r.seats:
                r.taken_seats = 0.0
            else:
                r.taken_seats = 100.0 * len(r.attendee_ids) / r.seats
                
    api.depends('start_date', 'duration')
    def _get_end_date(self):
        for r in self:
            if not (r.start_date and r.duration):
                r.end_date = r.start_date
                continue

            # Add duration to start_date, but: Monday + 5 days = Saturday, so
            # subtract one second to get on Friday instead
            duration = timedelta(days=r.duration, seconds=-1)
            r.end_date = r.start_date + duration

    def _set_end_date(self):
        for r in self:
            if not (r.start_date and r.end_date):
                continue

            # Compute the difference between dates, but: Friday - Monday = 4 days,
            # so add one day to get 5 days instead
            r.duration = (r.end_date - r.start_date).days + 1

                
    @api.onchange('seats', 'attendee_ids')
    def _verify_valid_seats(self):
        if self.seats < 0:
            return {
                'warning': {
                    'title': "Incorrect 'seats' value",
                    'message': "The number of available seats may not be negative",
                },
            }
        if self.seats < len(self.attendee_ids):
            return {
                'warning': {
                    'title': "Too many attendees",
                    'message': "Increase seats or remove excess attendees",
                },
            }
        
    @api.depends('attendee_ids')
    def _get_attendees_count(self):
        for r in self:
            r.attendees_count = len(r.attendee_ids)


    @api.constrains('instructor_id', 'attendee_ids')
    def _check_instructor_not_in_attendees(self):
        for r in self:
            if r.instructor_id and r.instructor_id in r.attendee_ids:
                raise exceptions.ValidationError("A session's instructor can't be an attendee")
Exemple #11
0
    def _onchange_partner(self):
        if self.partner_id:
            self.delivery_id = self.partner_id  # MAJ d'un autre champ
            # M2M : 2 possibilités :
            # - liste d'IDs, mais ça va AJOUTER les ids, comme (4, [IDs])
            # - [(6, 0, [IDs])], ce qui va remplacer les ids
            # (cf module product_category_tax dans akretion/odoo-usability)
            # On utilise un autre M2M pour le mettre à jour, on peut faire
            # self.champ_M2M_ids.ids -> ça donne la liste des IDs
            # M2O : recordset (ou ID)
            # O2M : exemple v10 dans purchase/models/account_invoice.py
            # méthode purchase_order_change()
            # là, Odoo va jouer automatiquement le @api.onchange du champ delivery_id
            # pas besoin d'appeler le onchange de delivery_id dans notre code
        # Here, all form values are set on self
        # assigned values are not written to DB, but returned to the client
        # It is not possible to output a warning
        # It is not possible to put a raise UserError()
        # in this function (it will crash odoo)
        res = {'warning':
            {'title': _('Be careful'),
            {'message': _('here is the msg')}}
        # pour un domaine
        res = {'domain': {
            'champ1': "[('product_id', '=', product_id)]",
            'champ2': "[]"},
            }
        return res
        # si je n'ai ni warning ni domain, je n'ai pas besoin de faire un return

    # Fonction on_change déclarée dans la vue form/tree
    @api.multi
    def product_id_change(self, cr, uid, ids, champ1, champ2, context):
        # ATTENTION : a priori, on ne doit pas utiliser ids dans le code de la
        # fonction, car quand on fait un on_change avant le save, ids = []
        # Dans la vue XML :
        # <field name="product_id"
        #        on_change="product_id_change(champ1, champ2, context)" />
        # Piège : quand un champ float est passé dans un on_change,
        # si la personne avait tapé un entier, il va être passé en argument en
        # tant que integer et non en tant que float!

        raise orm.except_orm()
        # => il ne remet PAS l'ancienne valeur qui a déclanché le on_change

        # Pour mettre à jour des valeurs :
        return {'value': {'champ1': updated_value1, 'champ2': updated_value2}}
        # => à savoir : les onchange de 'champ1' et 'champ2' sont joués à
        # leur tour car leur valeur a été changée
        # si ces nouveaux on_change changent le product_id,
        # le product_id_change ne sera pas rejoué

        # Pour mettre un domaine :
        return {'domain': {
            'champ1': "[('product_id', '=', product_id)]",
            'champ2': "[]"},
            }
        # l'intégralité du domaine est dans une string

        # Pour retourner un message de warning :
        return {'warning': {
            'title': _('Le titre du msg de warn'),
            'message': _("Ce que j'ai à te dire %s") % (text)}}
        # Pour ne rien faire
        return False  # return True, ça marche en 7.0 mais ça bug en 6.1

    # La fonction de calcul du champ function price_subtotal
    @api.one  # auto-loop decorator
    @api.depends('price_unit', 'discount', 'invoice_line_tax_id', 'quantity',
        'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id')
    # @api.depends est utilisé pour: invalidation cache, recalcul, onchange
    # donc, maintenant, le fait d'avoir un champ calculé fait qu'il est
    # automatiquement mis à jour dans la vue quand un de ses champs 'depends'
    # est modifié ! COOOOOL !
    # ATTENTION : si chgt de @api.depends, faire -u module !
    # Pour un one2many : ne PAS juste indiquer le nom du champ o2m, sinon il ne fait rien
    # il faut aussi indiquer un champ sur le O2M. Exemple : 'line_ids.request_id'
    # Apparemment, on peut mettre dans @api.depends un champ fonction stocké et ça va bien
    # faire le recalcul en cascade
    # (ça n'a pas l'air de marcher qd on met un api.depends sur un champ non stocké)
    def _compute_price(self):
        price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
        taxes = self.invoice_line_tax_id.compute_all(price, self.quantity, product=self.product_id, partner=self.invoice_id.partner_id)
        self.price_subtotal = taxes['total']  # calcul et stockage de la valeur
        self.second_field = 'iuit'  # calcul et stockage d'un 2e champ
                                    # equivalent de multi='pouet'
        # Pour un champ O2M ou M2M, envoyer un recordset multiple ou une liste d'IDS
        # pour un champ M2O, donner le recordset ou l'ID
        if self.invoice_id:
            self.price_subtotal = self.invoice_id.currency_id.round(self.price_subtotal)
        # Pas besoin de return !
        # on ne peut PAS faire un self.write({}) dans la fonction de calcul d'un champ fonction

    # Pour un champ fonction, on peut aussi faire @api.multi:
    # untaxed = fields.Float(compute='_amounts')
    # taxes = fields.Float(compute='_amounts')
    # total = fields.Float(compute='_amounts')
    @api.multi
    @api.depends('lines.amount', 'lines.taxes')
    def _amounts(self):
        for order in self:
            order.untaxed = sum(line.amount for line in order.lines)
            order.taxes = sum(line.taxes for line in order.lines)
            order.total = order.untaxed + order + taxes

    # Champ fonction inverse='_inverse_price'
    @api.one
    def _inverse_loud(self):
        self.name = (self.loud or '').lower()  # MAJ du ou des autres champs

    # Champ fonction search='_search_price'
    def _search_loud(self, operator, value):
        if value is not False:
            value = value.lower()
        today = fields.Date.context_today(self)
        self._cr.execute('SELECT id FROM [cur_obj] WHERE (fortress_type <> %s OR (fortress_type = %s AND effectivity_date is not null)) AND (end_date is null OR end_date > %s)', (today, ))
        res_ids = [x[0] for x in self._cr.fetchall()]
        res = [('id', 'in', res_ids)] # recherche sur les autres champs
        return res

    # Fonction default=_default_account
    @api.model
    def _default_account(self):
        return valeur_par_defaut
        # M2O : retourne un recordset ou un ID (ou False)
        # (NOTE: apparemment, en v8, il veut un ID)
        # OUTDATED (?) : ATTENTION, si on veut un M2O à False, il ne pas que la fonction
        #       _default_account retourne False mais self.env['..'].browse(False)
        # O2M : retourne une liste de dict contenant la valeur des champs
        # M2M : retourne un recordset multiple ?
        # date : string ou objet datetime

    # Fonction pour fields.selection
    @api.model
    def _type_list_get(self):
        return [('key1', _('String1')), ('key2', _('String2'))]

    ### CHAMPS
    # id, create_uid, write_uid, create_date et write_date
    # sont déjà utilisable dans le code python sans re-définition
    active = fields.Boolean(default=True)
    # Par défaut, string = nom du champ avec majuscule pour chaque début de mot
    login = fields.Char(
        string='Login', size=16, translate=True, required=True,
        help="My help message")
    display_name = fields.Char(
        string='Display Name', compute='_compute_display_name',
        readonly=True, store=True)
    comment = fields.Text(string='Comment', translate=True)
    html = fields.Html(string='report', translate=True)
    code_digits = fields.Integer(
        string='# of Digits', track_visibility='onchange', default=12,
        groups='base.group_user')
    # OU groups=['base.group_user', 'base.group_hr_manager']
    # groups = XMLID : restriction du read/write et invisible ds les vues
    sequence = fields.Integer(default=10)
    # track_visibility = always ou onchange
    amount_untaxed = fields.Float(
        'Amount untaxed', digits=dp.get_precision('Account'),
        group_operator="avg")  # Utile pour un pourcentage par exemple
    # digits=(precision, scale)   exemple (16, 2)
    # Scale est le nombre de chiffres après la virgule
    # quand le float est un fields.float ou un fields.function,
    # on met l'option : digits=dp.get_precision('Account')
    # Autres valeurs possibles pour get_precision : product/product_data.xml
    # Product Price, Discount, Stock Weight, Product Unit of Measure,
    # Product UoS (v8 only)
    # fields.Monetary is only in version >= 9.0
    debit = fields.Monetary(default=0.0, currency_field='company_currency_id')
    start_date = fields.Date(
        string='Start Date', copy=False, default=fields.Date.context_today,
        index=True)
    # similaire : fields.Datetime and fields.Time
    # index=True => the field will be indexed in the database
    # (much faster when you search on that field)
    type = fields.Selection([
        ('import', 'Import'),
        ('export', 'Export'),
        ], string="Type",
        default=lambda self: self._context.get('type', 'export'))
    # FIELDS.SELECTION ac selection dynamique :
    # type = fields.Selection('_type_list_get', string='Type', help='Pouet'),
    # Plus besoin de la double fonction pour que la 2e soit héritable
    # Pour ajouter des champs à un fields.Selection existant:
    # fields.Selection(
    #    selection_add=[('new_key1', 'My new key1'), ('new_key2', 'My New Key2')])
    picture = fields.Binary(string='Picture')
    # Pour fields.binary, il existe une option filters='*.png, *.gif',
    # qui restreint les formats de fichiers sélectionnables dans
    # la boite de dialogue, mais ça ne marche pas en GTK (on
    # ne peut rien sélectionner) et c'est pas supporté en Web, cf
    # https://bugs.launchpad.net/openobject-server/+bug/1076895
    picture_filename = fields.Char(string='Filename')
    # Les champs "picture" et "picture_filename" sont liés ensemble dans la vue
    # via la balise filename="picture_filename" sur le champ 'picture'
    # Il faut que le champ 'picture_filename' soit présent dans la vue
    # (il peut être invisible)
    # Pour un fichier à télécharger d'Odoo, le nom du fichier aura la valeur de
    # picture_filename
    # Pour un fichier à uploader dans Odoo, 'picture_filename' vaudra le nom
    # du fichier uploadé par l'utilisateur

    # Exemple de champ fonction stocké
    price_subtotal = fields.Float(
        string='Amount', digits= dp.get_precision('Account'),
        store=True, readonly=True, compute='_compute_price')
    # Exemple de champ function non stocké avec fonction inverse
    loud = fields.Char(
        store=False, compute='_compute_loud', inverse='_inverse_loud',
        search='_search_loud')
    account_id = fields.Many2one('account.account', string='Account',
        required=True, domain=[('type', 'not in', ['view', 'closed'])],
        default=lambda self: self._default_account())
        # L'utilisation de lambda permet d'hériter la fonction _default_account() sans
        # hériter le champ. Sinon, on peut aussi utiliser default=_default_account
    company_id = fields.Many2one(
        'res.company', string='Company',
        ondelete='cascade', required=True,
        default=lambda self: self.env['res.company']._company_default_get(
            'product.code'))
        # si on veut que tous les args soient nommés : comodel_name='res.company'
    user_id = fields.Many2one(
        'res.users', string='Salesman', default=lambda self: self.env.user)
    # ATTENTION : si j'ai déjà un domaine sur la vue,
    # c'est le domaine sur la vue qui prime !
    # ondelete='cascade' :
    # le fait de supprimer la company va supprimer l'objet courant !
    # ondelete='set null' (default)
    # si on supprime la company, le champ company_id est mis à 0
    # ondelete='restrict' :
    # si on supprime la company, ça déclanche une erreur d'intégrité !

    # Champ Relation
    company_currency_id = fields.Many2one(
        'res.currency', string='Currency', related='company_id.currency_id',
        store=True, compute_sudo=True)
    # ATTENTION, en nouvelle API, on ne peut PAS faire un fields.Char qui
    # soit un related d'un fields.Selection (bloque le démarrage d'Odoo
    # sans message d'erreur !)

    line_ids = fields.One2many(
        'product.code.line', 'parent_id', string='Product lines',
        states={'done': [('readonly', True)]}, copy=True)
        # OU comodel_name='product.code.line', inverse_name='parent_id'
    # 2e arg = nom du champ sur l'objet destination qui est le M20 inverse
    # en v8 :
    # copy=True pour que les lignes soient copiées lors d'un duplicate
    # sinon, mettre copy=False (ça ne peut être qu'un booléen)
    # Valeur par défaut du paramètre "copy": True for normal fields, False for
    # one2many and computed fields, including property fields and related fields
    # ATTENTION : pour que states={} marche sur le champ A et que le
    # champ A est dans la vue tree, alors il faut que le champ "state"
    # soit aussi dans la vue tree.

    partner_ids = fields.Many2many(
        'res.partner', 'product_code_partner_rel', 'code_id', 'partner_id',
        'Related Partners')
    # 2e arg = nom de la table relation
    # 3e arg ou id1 = nom de la colonne dans la table relation
    # pour stocker l'ID du product.code
    # 4e arg ou id2 = nom de la colonne dans la table relation
    # pour stocker l'ID du res.partner
    # OU
    partner_ids = fields.Many2many(
        'res.partner', column1='code_id', column2='partner_id',
        string='Related Partners')
    # OU
    partner_ids = fields.Many2many(
        'res.partner', string='Related Partners')
    # Pour les 2 dernières définitions du M2M, il ne faut pas avoir
    # plusieurs champs M2M qui pointent du même obj vers le même obj

    # Champ property: il suffit de définit le champ comme un champ normal
    # et d'ajouter un argument company_dependent=True
    # Quand on veut lire la valeur d'un champ property dans une société
    # qui n'est pas celle de l'utilisateur, il faut passer dans le context
    # 'force_company': 8  (8 = ID de la company)
    }