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