From 895fc32f2e54aaec586bef7a2f52e04a9cc72b8c Mon Sep 17 00:00:00 2001 From: hngqun <quandang2812@gmail.com> Date: Thu, 27 Jun 2024 10:52:36 +0700 Subject: [PATCH 1/2] update module --- s_license/__init__.py | 4 + s_license/__manifest__.py | 28 ++++ s_license/controllers/__init__.py | 3 + s_license/controllers/license_controller.py | 21 +++ s_license/models/__init__.py | 4 + s_license/models/license.py | 96 ++++++++++++ s_license/security/access_groups.xml | 13 ++ s_license/security/ir.model.access.csv | 7 + s_license/views/license_menu.xml | 10 ++ s_license/views/license_register.xml | 36 +++++ s_license/views/license_views.xml | 156 ++++++++++++++++++++ s_license/wizards/__init__.py | 3 + s_license/wizards/popup_wizard.py | 19 +++ s_license/wizards/popup_wizards.xml | 18 +++ 14 files changed, 418 insertions(+) create mode 100644 s_license/__init__.py create mode 100644 s_license/__manifest__.py create mode 100644 s_license/controllers/__init__.py create mode 100644 s_license/controllers/license_controller.py create mode 100644 s_license/models/__init__.py create mode 100644 s_license/models/license.py create mode 100644 s_license/security/access_groups.xml create mode 100644 s_license/security/ir.model.access.csv create mode 100644 s_license/views/license_menu.xml create mode 100644 s_license/views/license_register.xml create mode 100644 s_license/views/license_views.xml create mode 100644 s_license/wizards/__init__.py create mode 100644 s_license/wizards/popup_wizard.py create mode 100644 s_license/wizards/popup_wizards.xml diff --git a/s_license/__init__.py b/s_license/__init__.py new file mode 100644 index 0000000..09a4456 --- /dev/null +++ b/s_license/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from . import models +from . import controllers +from . import wizards diff --git a/s_license/__manifest__.py b/s_license/__manifest__.py new file mode 100644 index 0000000..df0888c --- /dev/null +++ b/s_license/__manifest__.py @@ -0,0 +1,28 @@ +{ + 'name': 'Custom License', + 'version': '1.0', + 'depends': ['base', 'mail', 'website'], + 'author': 'HngQun', + 'website': 'https://snine.vn/', + 'category': 'License Module', + 'description': """ + A module to demonstrate custom license in Odoo 16. + """, + 'data': [ + 'security/access_groups.xml', + 'security/ir.model.access.csv', + 'data/mail_template.xml', + 'data/ir_cron_data.xml', + 'wizards/popup_wizards.xml', + 'wizards/import_excel_wizards.xml', + 'views/license_views.xml', + 'views/license_register.xml', + 'views/license_menu.xml', + 'views/res_config_settings_views.xml', + 'reports/report_license.xml', + 'reports/report_license_template.xml', + ], + 'license': 'LGPL-3', + 'installable': True, + 'application': True, +} diff --git a/s_license/controllers/__init__.py b/s_license/controllers/__init__.py new file mode 100644 index 0000000..4457228 --- /dev/null +++ b/s_license/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import license_controller +from . import license_check diff --git a/s_license/controllers/license_controller.py b/s_license/controllers/license_controller.py new file mode 100644 index 0000000..22aa112 --- /dev/null +++ b/s_license/controllers/license_controller.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from odoo import http +from odoo.http import request +import json + +class LicenseController(http.Controller): + + @http.route('/viewlicense', type='http', auth='public', website=True) + def register_license_page(self, **kwargs): + return request.render('s_license.license_register') + + @http.route('/register_license', type='http', methods=['POST'], auth='public', website=True) + def register_license_submit(self, **kwargs): + request.env['license.management'].sudo().create({ + 'name': kwargs.get('name'), + 'start_date': kwargs.get('start_date'), + 'end_date': kwargs.get('end_date'), + 'key': kwargs.get('uuid'), + 'note': '', + }) + return request.render('s_license.license_register') diff --git a/s_license/models/__init__.py b/s_license/models/__init__.py new file mode 100644 index 0000000..8c9e7a2 --- /dev/null +++ b/s_license/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from . import license +from . import res_config_settings +from . import license_report diff --git a/s_license/models/license.py b/s_license/models/license.py new file mode 100644 index 0000000..6e6e99d --- /dev/null +++ b/s_license/models/license.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +from odoo import api, models, fields, _ +from odoo.exceptions import UserError, ValidationError +from datetime import timedelta +import re + +class LicenseManager(models.Model): + _name = 'license.management' + _description = 'License Management' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string='Name', required=True, tracking=True) + start_date = fields.Date(string='Start Date', required=True, tracking=True) + end_date = fields.Date(string='End Date', required=True, tracking=True) + key = fields.Char(string='Key', required=True, tracking=True) + note = fields.Text(string='Note') + status = fields.Selection([ + ('pending', 'Pending'), + ('active', 'Active'), + ('deactivate', 'Deactivate'), + ('cancelled', 'Cancelled'), + ], string='Status', tracking=True, readonly=True) + email = fields.Char(string='Email', tracking=True) + auto_renewal_date = fields.Date(string="Automatic Renewal Date", tracking=True, default=fields.Date.today(), readonly=True) + + @api.model + def create(self, vals): + if 'status' not in vals: + vals['status'] = 'pending' + record = super(LicenseManager, self).create(vals) + if record.start_date and record.end_date and record.end_date <= record.start_date: + raise UserError(_('The end date must be greater than the start date.')) + + record.notification_license_registration() + return record + + @api.constrains('key', 'name', 'start_date', 'end_date', 'email') + def action_check_constraints(self): + for record in self: + if self.search_count([('key', '=', record.key), ('id', '!=', record.id)]) > 0: + raise ValidationError(_('Key already exists!')) + if record.start_date and record.end_date and record.end_date <= record.start_date: + raise ValidationError(_('The end date must be greater than the start date.')) + if record.name and not re.match(r'^[a-zA-ZĂ€-Ỹà -ỹ\s]+$', record.name): + raise ValidationError(_('Name should not contain numbers or special characters.')) + + def action_show_popup(self, action): + return { + 'name': _('Reason'), + 'res_model': 'license.wizard', + 'view_mode': 'form', + 'target': 'new', + 'type': 'ir.actions.act_window', + 'context': { + 'default_state': action, + 'active_id': self.id, + } + } + + def action_active(self): + return self.action_show_popup('active') + + def action_cancel(self): + return self.action_show_popup('cancelled') + + def action_deactivate(self): + return self.action_show_popup('deactivate') + + def check_licenses_expiration(self): + today = fields.Date.today() + licenses = self.search([('status', '=', 'active'), ('end_date', '<=', today + timedelta(days=7))]) + template_id = self.env.ref('s_license.email_template_license_expiration').id + for license in licenses: + self.env['mail.template'].browse(template_id).send_mail(license.id, force_send=True, email_values={ + 'email_from': 'noreply@example.com', + 'email_to': 'quand816@gmail.com', + }) + + def auto_renew_licenses(self): + today = fields.Date.today() + licenses = self.search([('end_date', '=', today), ('status', '=', 'active')]) + months = self.env['ir.config_parameter'].sudo().get_param('s_license.renewal_period', 0) + for license in licenses: + new_end_date = fields.Date.from_string(license.end_date) + timedelta(days=30*int(months)) + new_auto_renewal_date = fields.Date.from_string(today) + timedelta(days=30*int(months)) + license.write({ + 'end_date': new_end_date, + 'auto_renewal_date': new_auto_renewal_date + }) + + def notification_license_registration(self): + template_id = self.env.ref('s_license.notification_template_license_registration').id + self.env['mail.template'].browse(template_id).send_mail(self.id, force_send=True, email_values={ + 'email_from': 'noreply@example.com', + 'email_to': 'quand816@gmail.com', + }) diff --git a/s_license/security/access_groups.xml b/s_license/security/access_groups.xml new file mode 100644 index 0000000..7605035 --- /dev/null +++ b/s_license/security/access_groups.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record model="ir.module.category" id="ls_category"> + <field name="name">Security for license</field> + <field name="sequence">45</field> + </record> + + <record id="group_license" model="res.groups"> + <field name="name">User</field> + <field name="category_id" ref="s_license.ls_category"/> + </record> + +</odoo> diff --git a/s_license/security/ir.model.access.csv b/s_license/security/ir.model.access.csv new file mode 100644 index 0000000..fbeac96 --- /dev/null +++ b/s_license/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink + +access_license,access_license,model_license_management,group_license,1,1,1,1 + +access_license_wizard,access_license_wizard,model_license_wizard,,1,1,1,1 + +access_license_import_wizard,access_license_import_wizard,model_license_import_wizard,,1,1,1,1 diff --git a/s_license/views/license_menu.xml b/s_license/views/license_menu.xml new file mode 100644 index 0000000..579a51e --- /dev/null +++ b/s_license/views/license_menu.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" ?> +<odoo> + <menuitem id="menu_license_root" name="Licenses" sequence="10"/> + + <menuitem id="menu_license_import" name="Import Excel Licenses" + parent="menu_license_root" action="action_license_import_wizard"/> + + <menuitem id="menu_license" name="Licenses" + parent="menu_license_root" action="action_license" sequence="10"/> +</odoo> diff --git a/s_license/views/license_register.xml b/s_license/views/license_register.xml new file mode 100644 index 0000000..f93716b --- /dev/null +++ b/s_license/views/license_register.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <template id="license_register" name="License Template"> + <t t-call="website.layout"> + <div class="container mt16 mb16"> + <h1>Register License</h1> + <br/> + <form id="register_form" action="/register_license" method="post"> + <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" /> + + <div class="form-group"> + <label for="name">Name:</label> + <input type="text" class="form-control" id="name" name="name"/> + </div> + + <div class="form-group"> + <label for="uuid">UUID:</label> + <input type="text" class="form-control" id="uuid" name="uuid" required="required"/> + </div> + + <div class="form-group"> + <label for="date_start">Start Date:</label> + <input type="date" class="form-control" id="start_date" name="start_date" required="required"/> + </div> + + <div class="form-group"> + <label for="date_end">End Date:</label> + <input type="date" class="form-control" id="end_date" name="end_date" required="required"/> + </div> + <br/> + <button type="submit" class="btn btn-primary">Register</button> + </form> + </div> + </t> + </template> +</odoo> diff --git a/s_license/views/license_views.xml b/s_license/views/license_views.xml new file mode 100644 index 0000000..17f7ab6 --- /dev/null +++ b/s_license/views/license_views.xml @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <data> + <record id="view_license_form" model="ir.ui.view"> + <field name="name">license.management.form</field> + <field name="model">license.management</field> + <field name="arch" type="xml"> + <form string="License"> + <header> + <button name="action_active" string="Confirm" + attrs="{'invisible': [('status', 'in', [False,'active','deactivate','cancelled'])]}" + type="object" class="oe_highlight"/> + + <button name="action_cancel" string="Cancel" + attrs="{'invisible': [('status', 'in', [False,'active','cancelled','deactivate'])]}" + type="object" class="oe_highlight"/> + + <button name="action_deactivate" string="Deactivate" + attrs="{'invisible': [('status', 'in', [False,'pending','cancelled','deactivate'])]}" + type="object" class="oe_highlight"/> + + <field name="status" widget="statusbar" + statusbar_visible="pending, active, cancelled, deactivate" + class="btn btn_primary"/> + + </header> + + <sheet> + <group> + <group> + <field name="name"/> + <field name="start_date"/> + <field name="end_date"/> + </group> + <group> + <field name="key"/> + <field name="email"/> + <field name="note"/> + </group> + </group> + </sheet> + + <notebook> + <page name="public" string="Work Information"> + + </page> + <page name="public" string="Demo Information"> + + </page> + </notebook> + + <div class="oe_chatter"> + <field name="message_follower_ids" groups="base.group_user" options="{'post_refresh': 'recipients'}"/> + <field name="activity_ids"/> + <field name="message_ids"/> + </div> + </form> + </field> + </record> + + <record id="view_license_search" model="ir.ui.view"> + <field name="name">license.management.search</field> + <field name="model">license.management</field> + <field eval="10" name="priority"/> + <field name="arch" type="xml"> + <search string="License Search"> + <field name="name"/> + <field name="start_date"/> + <field name="end_date"/> + <field name="key"/> + <field name="note"/> + <field name="status"/> + + <group expand='0' string='Filters'> + <separator/> + <filter name="pending_filter" string="pending" domain="[('status', '=', 'pending')]"/> + <filter name="cancelled_filter" string="cancelled" domain="[('status', '=', 'cancelled')]"/> + <filter name="active_filter" string="active" domain="[('status', '=', 'active')]"/> + <filter name="inactive_filter" string="inactive" domain="[('status', '=', 'inactive')]"/> + </group> + + <group expand='0' string='Group by...'> + <filter string='Status' name="status_group" context="{'group_by': 'status'}"/> + </group> + </search> + </field> + </record> + + <record id="view_license_tree" model="ir.ui.view"> + <field name="name">license.management.tree</field> + <field name="model">license.management</field> + <field name="arch" type="xml"> + <tree string="License"> + <field name="name"/> + <field name="start_date"/> + <field name="end_date"/> + <field name="key"/> + <field name="status"/> + </tree> + </field> + </record> + + <record id="view_license_kanban" model="ir.ui.view"> + <field name="name">license.management.kanban</field> + <field name="model">license.management</field> + <field name="arch" type="xml"> + <kanban default_group_by="status"> + <field name="name"/> + <field name="start_date"/> + <field name="end_date"/> + <field name="key"/> + <field name="status"/> + + <templates> + <t t-name="kanban-box"> + <div t-att-data-id="record.id" class="oe_kanban_card oe_kanban_global_click"> + <div class="oe_kanban_details"> + + <div class="o_kanban_record_title"> + <t t-esc="record.name.value" style="font-weight: bold; font-size: large;"/> + </div> + + <ul class="o_kanban_ul_details"> + <li t-if="record.start_date.value"> + <i aria-hidden="true"></i> + <strong> Start Date:</strong> <t t-esc="record.start_date.value"/> + </li> + <li t-if="record.end_date.value"> + <i aria-hidden="true"></i> + <strong> End Date:</strong> <t t-esc="record.end_date.value"/> + </li> + <li t-if="record.key.value"> + <i aria-hidden="true"></i> + <strong> Key:</strong> <t t-esc="record.key.value"/> + </li> + </ul> + + <div class="o_kanban_badge" t-attf-class="o_kanban_badge_#{record.status.value}"> + <span t-esc="record.status.value"/> + </div> + </div> + </div> + </t> + </templates> + </kanban> + </field> + </record> + + <record id="action_license" model="ir.actions.act_window"> + <field name="name">Licenses</field> + <field name="res_model">license.management</field> + <field name="view_mode">tree,kanban,form</field> + <field name="view_id" ref="view_license_tree"></field> + </record> + </data> +</odoo> diff --git a/s_license/wizards/__init__.py b/s_license/wizards/__init__.py new file mode 100644 index 0000000..e33d2ec --- /dev/null +++ b/s_license/wizards/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import popup_wizard +from . import import_excel_wizard diff --git a/s_license/wizards/popup_wizard.py b/s_license/wizards/popup_wizard.py new file mode 100644 index 0000000..f9df1ea --- /dev/null +++ b/s_license/wizards/popup_wizard.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api, _ + +class LicensePopupWizard(models.TransientModel): + _name = 'license.wizard' + _description = 'License Wizard' + + reason = fields.Text(string='Reason', required=True) + + def action_confirm(self): + active_id = self.env.context.get('active_id') + if active_id: + license_record = self.env['license.management'].browse(active_id) + today_str = fields.Date.today().strftime('%d/%m/%Y') + license_record.write({ + 'status': self.env.context.get('default_state'), + 'note': f"{today_str} - {self.reason}\n{license_record.note or ''}" + }) + return {'type': 'ir.actions.act_window_close'} diff --git a/s_license/wizards/popup_wizards.xml b/s_license/wizards/popup_wizards.xml new file mode 100644 index 0000000..5400675 --- /dev/null +++ b/s_license/wizards/popup_wizards.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <record id="view_license_wizard_form" model="ir.ui.view"> + <field name="name">license.wizard.form</field> + <field name="model">license.wizard</field> + <field name="arch" type="xml"> + <form string="License Wizard"> + <group> + <field name="reason"/> + </group> + <footer> + <button string="Confirm" type="object" name="action_confirm" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> +</odoo> -- GitLab From 192f430c07d68f5ba996b22955191ff06d872377 Mon Sep 17 00:00:00 2001 From: hngqun <quandang2812@gmail.com> Date: Thu, 27 Jun 2024 11:08:48 +0700 Subject: [PATCH 2/2] update module --- s_license/models/license.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/s_license/models/license.py b/s_license/models/license.py index 6e6e99d..747812e 100644 --- a/s_license/models/license.py +++ b/s_license/models/license.py @@ -4,6 +4,7 @@ from odoo.exceptions import UserError, ValidationError from datetime import timedelta import re + class LicenseManager(models.Model): _name = 'license.management' _description = 'License Management' @@ -44,6 +45,7 @@ class LicenseManager(models.Model): if record.name and not re.match(r'^[a-zA-ZĂ€-Ỹà -ỹ\s]+$', record.name): raise ValidationError(_('Name should not contain numbers or special characters.')) + def action_show_popup(self, action): return { 'name': _('Reason'), @@ -66,6 +68,7 @@ class LicenseManager(models.Model): def action_deactivate(self): return self.action_show_popup('deactivate') + def check_licenses_expiration(self): today = fields.Date.today() licenses = self.search([('status', '=', 'active'), ('end_date', '<=', today + timedelta(days=7))]) -- GitLab