Initial commit: Technical packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:51 +02:00
commit 3473fa71a0
873 changed files with 297766 additions and 0 deletions

View file

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mail_activity
from . import mail_activity_type
from . import note
from . import res_users

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields
class MailActivity(models.Model):
_inherit = "mail.activity"
note_id = fields.Many2one('note.note', string="Related Note", ondelete='cascade')

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields
class MailActivityType(models.Model):
_inherit = "mail.activity.type"
category = fields.Selection(selection_add=[('reminder', 'Reminder')])

View file

@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.tools import html2plaintext
from odoo.addons.web_editor.controllers.main import handle_history_divergence
class Stage(models.Model):
_name = "note.stage"
_description = "Note Stage"
_order = 'sequence'
name = fields.Char('Stage Name', translate=True, required=True)
sequence = fields.Integer(default=1)
user_id = fields.Many2one('res.users', string='Owner', required=True, ondelete='cascade', default=lambda self: self.env.uid)
fold = fields.Boolean('Folded by Default')
class Tag(models.Model):
_name = "note.tag"
_description = "Note Tag"
name = fields.Char('Tag Name', required=True, translate=True)
color = fields.Integer('Color Index')
_sql_constraints = [
('name_uniq', 'unique (name)', "Tag name already exists !"),
]
class Note(models.Model):
_name = 'note.note'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Note"
_order = 'sequence, id desc'
def _get_default_stage_id(self):
return self.env['note.stage'].search([('user_id', '=', self.env.uid)], limit=1)
name = fields.Text(
compute='_compute_name', string='Note Summary', store=True, readonly=False)
company_id = fields.Many2one('res.company')
user_id = fields.Many2one('res.users', string='Owner', default=lambda self: self.env.uid)
memo = fields.Html('Note Content')
sequence = fields.Integer('Sequence', default=0)
stage_id = fields.Many2one('note.stage', compute='_compute_stage_id',
inverse='_inverse_stage_id', string='Stage', default=_get_default_stage_id)
stage_ids = fields.Many2many('note.stage', 'note_stage_rel', 'note_id', 'stage_id',
string='Stages of Users', default=_get_default_stage_id)
open = fields.Boolean(string='Active', default=True)
date_done = fields.Date('Date done')
color = fields.Integer(string='Color Index')
tag_ids = fields.Many2many('note.tag', 'note_tags_rel', 'note_id', 'tag_id', string='Tags')
# modifying property of ``mail.thread`` field
message_partner_ids = fields.Many2many(compute_sudo=True)
@api.depends('memo')
def _compute_name(self):
""" Read the first line of the memo to determine the note name """
for note in self:
if note.name:
continue
text = html2plaintext(note.memo) if note.memo else ''
note.name = text.strip().replace('*', '').split("\n")[0]
def _compute_stage_id(self):
first_user_stage = self.env['note.stage'].search([('user_id', '=', self.env.uid)], limit=1)
for note in self:
for stage in note.stage_ids.filtered(lambda stage: stage.user_id == self.env.user):
note.stage_id = stage
# note without user's stage
if not note.stage_id:
note.stage_id = first_user_stage
def _inverse_stage_id(self):
for note in self.filtered('stage_id'):
note.stage_ids = note.stage_id + note.stage_ids.filtered(lambda stage: stage.user_id != self.env.user)
@api.model
def name_create(self, name):
return self.create({'memo': name}).name_get()[0]
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
if groupby and groupby[0] == "stage_id" and (len(groupby) == 1 or lazy):
stages = self.env['note.stage'].search([('user_id', '=', self.env.uid)])
if stages:
# if the user has some stages
result = []
for stage in stages:
# notes by stage for stages user
nb_stage_counts = self.search_count(domain + [('stage_ids', '=', stage.id)])
result.append({
'__context': {'group_by': groupby[1:]},
'__domain': domain + [('stage_ids.id', '=', stage.id)],
'stage_id': (stage.id, stage.name),
'stage_id_count': nb_stage_counts,
'__count': nb_stage_counts,
'__fold': stage.fold,
})
# note without user's stage
nb_notes_ws = self.search_count(domain + [('stage_ids', 'not in', stages.ids)])
if nb_notes_ws:
# add note to the first column if it's the first stage
dom_not_in = ('stage_ids', 'not in', stages.ids)
if result and result[0]['stage_id'][0] == stages[0].id:
dom_in = result[0]['__domain'].pop()
result[0]['__domain'] = domain + ['|', dom_in, dom_not_in]
result[0]['stage_id_count'] += nb_notes_ws
result[0]['__count'] += nb_notes_ws
else:
# add the first stage column
result = [{
'__context': {'group_by': groupby[1:]},
'__domain': domain + [dom_not_in],
'stage_id': (stages[0].id, stages[0].name),
'stage_id_count': nb_notes_ws,
'__count': nb_notes_ws,
'__fold': stages[0].name,
}] + result
else: # if stage_ids is empty, get note without user's stage
nb_notes_ws = self.search_count(domain)
if nb_notes_ws:
result = [{ # notes for unknown stage
'__context': {'group_by': groupby[1:]},
'__domain': domain,
'stage_id': False,
'stage_id_count': nb_notes_ws,
'__count': nb_notes_ws
}]
else:
result = []
return result
return super(Note, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
def action_close(self):
return self.write({'open': False, 'date_done': fields.date.today()})
def action_open(self):
return self.write({'open': True})
def write(self, vals):
if len(self) == 1:
handle_history_divergence(self, 'memo', vals)
return super(Note, self).write(vals)

View file

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, models, modules, _
_logger = logging.getLogger(__name__)
class Users(models.Model):
_name = 'res.users'
_inherit = ['res.users']
@api.model_create_multi
def create(self, vals_list):
users = super().create(vals_list)
user_group_id = self.env['ir.model.data']._xmlid_to_res_id('base.group_user')
# for new employee, create his own 5 base note stages
users.filtered_domain([('groups_id', 'in', [user_group_id])])._create_note_stages()
return users
@api.model
def _init_data_user_note_stages(self):
emp_group_id = self.env.ref('base.group_user').id
query = """
SELECT res_users.id
FROM res_users
WHERE res_users.active IS TRUE AND EXISTS (
SELECT 1 FROM res_groups_users_rel WHERE res_groups_users_rel.gid = %s AND res_groups_users_rel.uid = res_users.id
) AND NOT EXISTS (
SELECT 1 FROM note_stage stage WHERE stage.user_id = res_users.id
)
GROUP BY id"""
self.env.cr.execute(query, (emp_group_id,))
uids = [res[0] for res in self.env.cr.fetchall()]
self.browse(uids)._create_note_stages()
def _create_note_stages(self):
for num in range(4):
stage = self.env.ref('note.note_stage_%02d' % (num,), raise_if_not_found=False)
if not stage:
break
for user in self:
stage.sudo().copy(default={'user_id': user.id})
else:
_logger.debug("Created note columns for %s", self)
@api.model
def systray_get_activities(self):
""" If user have not scheduled any note, it will not appear in activity menu.
Making note activity always visible with number of notes on label. If there is no notes,
activity menu not visible for note.
"""
activities = super(Users, self).systray_get_activities()
notes_count = self.env['note.note'].search_count([('user_id', '=', self.env.uid)])
if notes_count:
note_index = next((index for (index, a) in enumerate(activities) if a["model"] == "note.note"), None)
note_label = _('Notes')
if note_index is not None:
activities[note_index]['name'] = note_label
else:
activities.append({
'id': self.env['ir.model']._get('note.note').id,
'type': 'activity',
'name': note_label,
'model': 'note.note',
'icon': modules.module.get_module_icon(self.env['note.note']._original_module),
'total_count': 0,
'today_count': 0,
'overdue_count': 0,
'planned_count': 0
})
return activities