Merge branch 'dev_odex25_base' into dev_base_temp

This commit is contained in:
MohamedGad100 2024-08-19 15:33:52 +03:00 committed by GitHub
commit f0143fc6df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
349 changed files with 50161 additions and 4609 deletions

View File

@ -0,0 +1,47 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
Cron Failure Notification
=========================
* Cron Failure Notification module helps to notify the admin by sending an email, if any failure occurred in scheduled actions in odoo 14.
Installation
============
- www.odoo.com/documentation/14.0/setup/install.html
- Install our custom addon
License
==========
General Public License, Version 3 (AGPL v3).
(https://www.gnu.org/licenses/agpl-3.0-standalone.html)
Company
==========
`Cybrosys Techno Solutions <https://cybrosys.com/>`__
Credits
============
* Developer: (V14) Mohammed Dilshad Tk , Contact: odoo@cybrosys.com
Contacts
==========
* Mail Contact : odoo@cybrosys.com
Bug Tracker
==========
Bugs are tracked on GitHub Issues. In case of trouble, please check there if
your issue has already been reported.
Maintainer
==========
.. image:: https://cybrosys.com/images/logo.png
:target: https://cybrosys.com
This module is maintained by Cybrosys Technologies.
For support and more information, please visit https://www.cybrosys.com
Further information
===================
HTML Description: `<static/description/index.html>`__

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: MOHAMMED DILSHAD TK (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from . import models

View File

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: MOHAMMED DILSHAD TK (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
{
'name': "Cron Failure Notification",
'version': '14.0.1.0.0',
'category': 'Extra Tools',
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions',
'website': 'https://www.cybrosys.com',
'summary': """This module helps to send notification on scheduled
actions failures in odoo 14""",
'description': """Cron Failure Notification helps to notify the admin
about the cron failure as a mail with a list of failed
cron details""",
'depends': ['mail'],
'data': [
'security/ir.model.access.csv',
'report/cron_failure_notification_templates.xml',
'report/cron_failure_notification_reports.xml',
'data/mail_template_data.xml',
'data/ir_cron_data.xml',
'views/failure_history_views.xml',
],
'images': ['static/description/banner.png'],
'license': 'AGPL-3',
'installable': True,
'auto_install': False,
'application': False,
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Ir cron fail mail send schedule action-->
<data noupdate="1">
<record id="ir_cron_fail_report_mail" model="ir.cron">
<field name="name">Mail: Cron Error Notification</field>
<field name="model_id" ref="model_ir_cron"/>
<field name="state">code</field>
<field name="code">model.mail_send_cron()</field>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="1">
<!-- Ir cron fail mail template -->
<record id="mail_template_cron_error" model="mail.template">
<field name="name">Cron Action Error</field>
<field name="model_id" ref="cron_failure_notification.model_ir_cron"/>
<field name="subject">Cron Actions Failed</field>
<field name="email_to">${ ctx[ 'admin_mail' ] }</field>
<field name="report_template" ref="cron_failure_notification.cron_fail_pdf_report"/>
<field name="body_html" type="html">
<p>Dear Admin,</p>
<p>Here is attaching failures from scheduled cron jobs on our server,
so please quickly take an action for this.
</p><p>Thank You.</p>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,7 @@
## Module <cron_failure_notification>
#### 17.10.2023
#### Version 14.0.1.0.0
#### ADD
- Initial commit for Cron Failure Notification

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: MOHAMMED DILSHAD TK (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from . import failure_history
from . import ir_cron

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: MOHAMMED DILSHAD TK (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
from odoo import fields, models
class FailureHistory(models.Model):
"""Creates failure history"""
_name = 'failure.history'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Failure History'
name = fields.Char(string='Name', required=True, help="Name of failed cron")
error = fields.Char(string='Error Details', help="Brief details of error")

View File

@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
# ################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: MOHAMMED DILSHAD TK (odoo@cybrosys.com)
#
# You can modify it under the terms of the GNU AFFERO
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
#
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
# (AGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
import datetime
import logging
import time
import odoo
from odoo import api, models, _
from odoo.fields import Datetime, _logger
from odoo.exceptions import ValidationError
class IrCron(models.Model):
""" Inherits ir cron to send mail to admin if any cron actions failed. """
_name = 'ir.cron'
_description = "Ir Cron"
_inherit = ['ir.cron', 'mail.thread']
@api.model
def _callback(self, cron_name, server_action_id, job_id):
""" Run the method associated to a given job. It takes care of logging
and exception handling. Note that the user running the server action
is the user calling this method. """
try:
if self.pool != self.pool.check_signaling():
# the registry has changed, reload self in the new registry
self.env.reset()
log_depth = (None if _logger.isEnabledFor(logging.DEBUG) else 1)
odoo.netsvc.log(_logger, logging.DEBUG, 'cron.object.execute',
(self._cr.dbname, self._uid, '*', cron_name,
server_action_id), depth=log_depth)
start_time = False
_logger.info('Starting job `%s`.', cron_name)
if _logger.isEnabledFor(logging.DEBUG):
start_time = time.time()
self.env['ir.actions.server'].browse(server_action_id).run()
_logger.info('Job `%s` done.', cron_name)
if start_time and _logger.isEnabledFor(logging.DEBUG):
end_time = time.time()
_logger.debug('%.3fs (cron %s, server action %d with uid %d)',
end_time - start_time, cron_name,
server_action_id, self.env.uid)
self.pool.signal_changes()
except Exception as exception:
self.pool.reset_changes()
_logger.exception(
"Call from cron %s for server action #%s failed in Job #%s",
cron_name, server_action_id, job_id)
if exception:
self.env['failure.history'].create({
'name': cron_name,
'error': str(exception),
})
def mail_send_cron(self):
""" If any cron's failed a notification email will send to admin """
start_of_day = Datetime.today()
end_of_day = datetime.datetime.combine(start_of_day, datetime.time.max)
if self.env['failure.history'].search(
[('create_date', '>', start_of_day),
('create_date', '<', end_of_day)]):
admin_mail = self.env['res.groups'].search(
[('category_id', '=', 'Administration'),
('name', '=', 'Access Rights')]).users.login
email_values = {'admin_mail': admin_mail}
mail_template = self.env.ref(
'cron_failure_notification.mail_template_cron_error')
mail_template.with_context(email_values).send_mail(self.id,
force_send=True)
else:
raise ValidationError(_('No failures in cron actions'))

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Ir cron report action-->
<record id="cron_fail_pdf_report" model="ir.actions.report">
<field name="name">Cron failure report</field>
<field name="model">ir.cron</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">cron_failure_notification.report_logs_details</field>
<field name="report_file">cron_failure_notification.report_logs_details</field>
<field name="binding_model_id" ref="model_ir_cron"/>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Cron action failure pdf template-->
<template id="cron_fail_pdf">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<div class="page">
<center>
<h2>Cron Failure Report</h2>
</center>
<br/>
<br/>
<div class="oe_structure" style="">
<table class="table table-bordered">
<div class="card-header">
<tr>
<th class="text-center">Cron Name</th>
<th class="text-center">Date and Time of
Run
</th>
<th class="text-center">Error Details</th>
</tr>
</div>
<t t-set="start_time"
t-value="datetime.datetime.combine(datetime.date.today(), datetime.time.min).strftime('%Y-%m-%d %H:%M:%S')"/>
<t t-set="end_time"
t-value="datetime.datetime.combine(datetime.date.today(), datetime.time.max).strftime('%Y-%m-%d %H:%M:%S')"/>
<t t-set="failure_record"
t-value="request.env['failure.history'].search([('create_date', '&gt;=', start_time), ('create_date', '&lt;=', end_time )])"/>
<t t-foreach="failure_record" t-as="history">
<tr>
<td>
<span t-esc="history.name"/>
</td>
<td>
<span t-esc="history.create_date"/>
</td>
<td style="color:#ea5252;">
<h5>
<span t-esc="history.error"/>
</h5>
</td>
</tr>
</t>
</table>
</div>
</div>
</t>
</t>
</template>
<template id="report_logs_details">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<t t-call="cron_failure_notification.cron_fail_pdf"/>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_failure_history,failure.history,model_failure_history,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_failure_history failure.history model_failure_history base.group_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,593 @@
<div style="background-color: #714B67; min-height: 600px; width: 100%; padding: 15px; position: relative;">
<!-- TITLE BAR -->
<div class="d-flex align-items-center justify-content-between"
style="border-bottom: 1px solid #875A7B; padding: 15px; display: flex; justify-content: space-between; align-items: center;">
<img src="assets/misc/cybrosys-logo.png" width="42" height="42"
style="width: 42px; height: 42px;"/>
<div>
<div style="color: #7C7BAD; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
class="mr-2">
<i class="fa fa-check mr-1"></i>Community
</div>
<div style="color: #875A7B; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
class="mr-2">
<i class="fa fa-check mr-1"></i>Enterprise
</div>
<div style="color: #017E84; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
class="mr-2">
<i class="fa fa-check mr-1"></i>Odoo.sh
</div>
</div>
</div>
<!-- END OF TITLE BAR -->
<!-- APP HERO -->
<div class="container">
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
<h1 style="color: #FFFFFF; font-weight: bolder; font-size: 50px; text-align: center; margin-top: 50px;">
Cron Failure Notification</h1>
<p style="color:#FFFFFF; padding: 8px 15px; text-align: center; font-size: 24px;">
Notify The Admin Through Mail With List Of Failures</p>
<!-- END OF APP HERO -->
<img src="assets/screenshots/hero.gif"
style="width: 75%; height: auto; position: absolute; margin-left: auto; margin-right: auto; top: 45%; left: 12%; right: auto;margin-top: 8%;"/>
</div>
</div>
</div>
</div>
<!-- NAVIGATION SECTION -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px; margin-top: 300px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/compass.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Explore This
Module</h2>
</div>
<div class="row my-4" style="font-family: 'Montserrat', sans-serif;">
<div class="col-sm-12 col-md-6 my-3">
<a href="#overview">
<div class="d-flex justify-content-between align-items-center"
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
<div>
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Overview</span>
<span
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">Learn
more about this
module</span>
</div>
<img src="assets/misc/right-arrow.png" width="36" height="36"/>
</div>
</a>
</div>
<div class="col-sm-12 col-md-6 my-3">
<a href="#features">
<div class="d-flex justify-content-between align-items-center"
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
<div>
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Features</span>
<span
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View
features of this
module</span>
</div>
<img src="assets/misc/right-arrow.png" width="36" height="36"/>
</div>
</a>
</div>
<div class="col-sm-12 col-md-6 my-3">
<a href="#screenshots">
<div class="d-flex justify-content-between align-items-center"
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
<div>
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Screenshots</span>
<span
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View
screenshots for this
module</span>
</div>
<img src="assets/misc/right-arrow.png" width="36" height="36"/>
</div>
</a>
</div>
</div>
<!-- END OF NAVIGATION SECTION -->
<!-- OVERVIEW SECTION -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="overview">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/pie-chart.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Overview
</h2>
</div>
<div class="row"
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;">
<div class="col-sm-12 py-4">
Cron failure notification module helps to notify the failures of the
cron action failures through sending an
email with attached pdf containing
the list of failed cron actions.
</div>
</div>
<!-- END OF OVERVIEW SECTION -->
<!-- FEATURES SECTION -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="features">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/features.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Features
</h2>
</div>
<div class="row"
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;">
<div class="col-sm-12 col-md-6">
<div class="d-flex align-items-center"
style="margin-top: 30px; margin-bottom: 30px">
<img src="assets/misc/check-box.png" class="mr-2"/>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Creating Failure History.</span>
</div>
<div class="d-flex align-items-center"
style="margin-top: 30px; margin-bottom: 30px">
<img src="assets/misc/check-box.png" class="mr-2"/>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Notify the Admin Through Mail.</span>
</div>
<div class="d-flex align-items-center"
style="margin-top: 30px; margin-bottom: 30px">
<img src="assets/misc/check-box.png" class="mr-2"/>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">The Mail Contains Details of Cron Failure History with a Pdf Report.</span>
</div>
<div class="d-flex align-items-center"
style="margin-top: 40px; margin-bottom: 40px">
<img src="assets/misc/check-box.png" class="mr-2"/>
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Mail will Send all Day Only if there is a Failure.</span>
</div>
</div>
</div>
<!-- END OF FEATURES SECTION -->
<!-- SCREENSHOTS SECTION -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"
id="screenshots">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/pictures.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Screenshots
</h2>
</div>
<div class="row">
<div class="col-sm-12">
<div style="display: block; margin: 30px auto;">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Cron Action that Sends the Admin with Failed cron History.</h3>
<img src="assets/screenshots/c1.png" class="img-thumbnail">
</div>
<div style="display: block; margin: 30px auto;">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Failure History</h3>
<img src="assets/screenshots/c2.png" class="img-thumbnail">
</div>
<div style="display: block; margin: 30px auto;">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">
Pdf Attached with Mail</h3>
<img src="assets/screenshots/4.png" class="img-thumbnail">
</div>
</div>
</div>
<!-- END OF SCREENSHOTS SECTION -->
<!-- Suggested PRODUCTS -->
<div class="row">
<div class="col-lg-12 d-flex flex-column justify-content-center"
style="text-align: center; padding: 2.5rem 1rem !important;">
<h2 style="color: #212529 !important;">Suggested Products</h2>
<hr
style="border: 3px solid #714B67 !important; background-color: #714B67 !important; width: 80px !important; margin-bottom: 2rem !important;"/>
<div id="demo1" class="row carousel slide" data-ride="carousel">
<!-- The slideshow -->
<div class="carousel-inner">
<div class="carousel-item active" style="min-height:0px">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/14.0/export_stockinfo_xls/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/export_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/14.0/dashboard_pos/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/pos_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/14.0/product_approval_management/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/approval_image.png">
</div>
</a>
</div>
</div>
<div class="carousel-item" style="min-height:0px">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/14.0/base_account_budget/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/budget_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/14.0/shopify_odoo_connector/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/shopify_image.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16"
style="float:left">
<a href="https://apps.odoo.com/apps/modules/14.0/odoo11_magento2/"
target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block"
style="border-top-left-radius:10px; border-top-right-radius:10px"
src="./assets/modules/magento_image.png">
</div>
</a>
</div>
</div>
</div>
<!-- Left and right controls -->
<a class="carousel-control-prev" href="#demo1" data-slide="prev"
style="left:-25px;width: 35px;color: #000;"> <span
class="carousel-control-prev-icon"><i
class="fa fa-chevron-left"
style="font-size:24px"></i></span> </a> <a
class="carousel-control-next" href="#demo1" data-slide="next"
style="right:-25px;width: 35px;color: #000;">
<span class="carousel-control-next-icon"><i
class="fa fa-chevron-right"
style="font-size:24px"></i></span>
</a>
</div>
</div>
</div>
<!-- END OF RELATED PRODUCTS -->
<!-- OUR SERVICES -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/star.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Our Services
</h2>
</div>
<div class="container my-5">
<div class="row">
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/cogs.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Customization</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/wrench.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/lifebuoy.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Support</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/user.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Hire
Odoo
Developer</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/puzzle.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Integration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/update.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Migration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/consultation.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Consultancy</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/training.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/license.png" class="img-responsive"
height="48px" width="48px">
</div>
<h6 class="text-center"
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Licensing Consultancy</h6>
</div>
</div>
</div>
<!-- END OF OUR SERVICES -->
<!-- OUR INDUSTRIES -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/corporate.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Our
Industries
</h2>
</div>
<div class="container my-5">
<div class="row">
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/trading-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Trading
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easily procure
and
sell your products</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/pos-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
POS
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easy
configuration
and convivial experience</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/education-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Education
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
A platform for
educational management</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/manufacturing-black.png"
class="img-responsive mb-3" height="48px"
width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Manufacturing
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Plan, track and
schedule your operations</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/ecom-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
E-commerce &amp; Website
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Mobile
friendly,
awe-inspiring product pages</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/service-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Service Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Keep track of
services and invoice</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/restaurant-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Restaurant
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Run your bar or
restaurant methodically</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/hotel-black.png"
class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Hotel Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
An
all-inclusive
hotel management application</p>
</div>
</div>
</div>
</div>
<!-- END OF OUR INDUSTRIES -->
<!-- SUPPORT -->
<div class="d-flex align-items-center"
style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/customer-support.png"/>
</div>
<h2 class="mt-2"
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">
Support
</h2>
</div>
<div class="container mt-5">
<div class="row">
<div class="col-sm-12 col-md-6">
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;">
<div class="mr-4"
style="background-color: #714B67; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;">
<img src="assets/misc/support.png" height="48" width="48"
style="width: 42px; height: 42px;"/>
</div>
<div>
<h4>Need Help?</h4>
<p style="line-height: 100%;">Got questions or need help?
Get in touch.</p>
<a href="mailto:odoo@cybrosys.com">
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">
odoo@cybrosys.com</p>
</a>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;">
<div class="mr-4"
style="background-color: #2AC44D; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;">
<img src="assets/misc/whatsapp.png" height="52" width="52"
style="width: 52px; height: 52px;"/>
</div>
<div>
<h4>WhatsApp</h4>
<p style="line-height: 100%;">Say hi to us on WhatsApp!</p>
<a href="https://api.whatsapp.com/send?phone=918606827707">
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">
+91 86068
27707</p>
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 my-5 d-flex justify-content-center align-items-center">
<img src="assets/misc/logo.png" width="144" height="31"
style="width:144px; height: 31px; margin-top: 40px;"/>
</div>
</div>
</div>
<!-- END OF SUPPORT -->

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Failure history action-->
<record id="failure_history_action" model="ir.actions.act_window">
<field name="name">Failure History</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">failure.history</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
No Cron Failures Yet.
</p>
<p>
Failures on cron actions shows here.
</p>
</field>
</record>
<!-- Failure history tree view-->
<record id="failure_history_view_tree" model="ir.ui.view">
<field name="name">failure.history.view.tree</field>
<field name="model">failure.history</field>
<field name="arch" type="xml">
<tree create="false">
<field name="name"/>
</tree>
</field>
</record>
<!--Failure history form view-->
<record id="failure_history_view_form" model="ir.ui.view">
<field name="name">failure.history.view.form</field>
<field name="model">failure.history</field>
<field name="arch" type="xml">
<form string="failure_history_form">
<sheet>
<group>
<field name="name"/>
</group>
<notebook>
<page string="Error Information">
<group>
<field name="error"/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"
groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<!-- Failure history menu in technical menu in settings -->
<menuitem id="failure_history_menu" name="Cron Failure History"
parent="base.menu_automation"
action="failure_history_action"/>
</odoo>

View File

@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
{
'name': 'Dashboard Ninja',
'name': 'Dashboard Ninja With AI',
'summary': """
'summary': """
Ksolves Dashboard Ninja gives you a wide-angle view of your business that you might have missed. Get smart visual data with interactive and engaging dashboards for your Odoo ERP. Odoo Dashboard, CRM Dashboard, Inventory Dashboard, Sales Dashboard, Account Dashboard, Invoice Dashboard, Revamp Dashboard, Best Dashboard, Odoo Best Dashboard, Odoo Apps Dashboard, Best Ninja Dashboard, Analytic Dashboard, Pre-Configured Dashboard, Create Dashboard, Beautiful Dashboard, Customized Robust Dashboard, Predefined Dashboard, Multiple Dashboards, Advance Dashboard, Beautiful Powerful Dashboards, Chart Graphs Table View, All In One Dynamic Dashboard, Accounting Stock Dashboard, Pie Chart Dashboard, Modern Dashboard, Dashboard Studio, Dashboard Builder, Dashboard Designer, Odoo Studio. Revamp your Odoo Dashboard like never before! It is one of the best dashboard odoo apps in the market.
""",
'description': """
'description': """
Dashboard Ninja v14.0,
Odoo Dashboard,
Dashboard,
@ -63,35 +63,48 @@ Dashboard Ninja v14.0,
odoo dashboard module
""",
'author': 'Ksolves India Ltd.',
'author': 'Ksolves India Ltd.',
'license': 'OPL-1',
'license': 'OPL-1',
'currency': 'EUR',
'currency': 'EUR',
'price': '272.25',
'website': 'https://store.ksolves.com/',
'price': '287.19',
'maintainer': 'Ksolves India Ltd.',
'website': 'https://store.ksolves.com/',
'live_test_url': 'https://dn15demo.kappso.com/web/demo_login',
'maintainer': 'Ksolves India Ltd.',
'category': 'Tools',
'live_test_url': 'https://dn15demo.kappso.com/web#cids=1&menu_id=599&ks_dashboard_id=1&action=835',
'version': '14.0.1.7.2',
'category': 'Tools',
'support': 'sales@ksolves.com',
'version': '14.0.2.0.0',
'images': ['static/description/banner.gif'],
'support': 'sales@ksolves.com',
'depends': ['base', 'web', 'base_setup', 'bus'],
'images': ['static/description/Independence_sale.jpg'],
'data': ['security/ir.model.access.csv', 'security/ks_security_groups.xml', 'data/ks_default_data.xml', 'wizard/ks_create_dashboard_wizard_view.xml', 'wizard/ks_duplicate_dashboard_wiz_view.xml', 'views/ks_dashboard_ninja_view.xml', 'views/ks_dashboard_ninja_item_view.xml', 'views/ks_dashboard_ninja_assets.xml', 'views/ks_dashboard_action.xml', 'views/ks_import_dashboard_view.xml'],
'depends': ['base', 'web', 'base_setup', 'bus'],
'qweb': ['static/src/xml/ks_dn_global_filter.xml', 'static/src/xml/ks_dashboard_ninja_templates.xml', 'static/src/xml/ks_dashboard_ninja_item_templates.xml', 'static/src/xml/ks_dashboard_ninja_item_theme.xml', 'static/src/xml/ks_widget_toggle.xml', 'static/src/xml/ks_dashboard_pro.xml', 'static/src/xml/ks_import_list_view_template.xml', 'static/src/xml/ks_quick_edit_view.xml', 'static/src/xml/ks_to_do_template.xml'],
'data': ['security/ir.model.access.csv', 'security/ks_security_groups.xml', 'data/ks_default_data.xml',
'data/dn_data.xml',
'wizard/ks_create_dashboard_wizard_view.xml', 'wizard/ks_duplicate_dashboard_wiz_view.xml',
'views/ks_dashboard_ninja_view.xml', 'views/ks_dashboard_ninja_item_view.xml', 'views/res_settings.xml',
'views/ks_dashboard_ninja_assets.xml',
'views/ks_dashboard_action.xml',
'views/ks_import_dashboard_view.xml', 'views/ks_ai_dashboard.xml', 'views/ks_key_fetch.xml',
'views/ks_whole_ai_dashboard.xml'],
'demo': ['demo/ks_dashboard_ninja_demo.xml'],
'qweb': ['static/src/xml/ks_dn_global_filter.xml', 'static/src/xml/ks_dashboard_ninja_templates.xml',
'static/src/xml/ks_dashboard_ninja_item_templates.xml', 'static/src/xml/ks_ai_dash_button.xml',
'static/src/xml/ks_dashboard_ninja_item_theme.xml',
'static/src/xml/ks_widget_toggle.xml', 'static/src/xml/ks_dashboard_pro.xml',
'static/src/xml/ks_import_list_view_template.xml', 'static/src/xml/ks_quick_edit_view.xml',
'static/src/xml/ks_to_do_template.xml', 'static/src/xml/ks_keyword_selection.xml'],
'uninstall_hook': 'uninstall_hook',
'demo': ['demo/ks_dashboard_ninja_demo.xml'],
'uninstall_hook': 'uninstall_hook',
}

View File

@ -32,9 +32,75 @@ def ks_get_date(ks_date_filter_selection, self, type):
series = ks_date_filter_selection
if ks_date_filter_selection in ['t_fiscal_year', 'n_fiscal_year', 'ls_fiscal_year']:
return eval("ks_date_series_" + series.split("_")[0])(series.split("_")[1], timezone, type,self)
function_name = globals()["ks_date_series_" + series.split("_")[0]]
return function_name(series.split("_")[1], timezone, type, self)
else:
return eval("ks_date_series_" + series.split("_")[0])(series.split("_")[1], timezone, type, self)
function_name = globals()["ks_date_series_" + series.split("_")[0]]
return function_name(series.split("_")[1], timezone, type, self)
def ks_date_series_td(ks_date_selection, timezone, type, self=None):
ks_function_name = globals()["ks_get_date_range_from_td_" + ks_date_selection]
return ks_function_name(timezone, type, self)
def ks_get_date_range_from_td_year(timezone, type,self):
ks_date_data = {}
date = datetime.now(pytz.timezone(timezone))
year = date.year
start_date = datetime(year, 1, 1)
end_date = date
if type == 'date':
ks_date_data["selected_start_date"] = datetime.strptime(start_date.strftime("%Y-%m-%d"), '%Y-%m-%d')
ks_date_data["selected_end_date"] = datetime.strptime(end_date.strftime("%Y-%m-%d"), '%Y-%m-%d')
else:
ks_date_data["selected_start_date"] = ks_convert_into_utc(start_date, timezone)
ks_date_data["selected_end_date"] = ks_convert_into_utc(end_date, timezone)
return ks_date_data
def ks_get_date_range_from_td_month(timezone, type,self):
ks_date_data = {}
date = datetime.now(pytz.timezone(timezone))
year = date.year
month = date.month
start_date = datetime(year, month, 1)
end_date = date
if type == 'date':
ks_date_data["selected_start_date"] = datetime.strptime(start_date.strftime("%Y-%m-%d"), '%Y-%m-%d')
ks_date_data["selected_end_date"] = datetime.strptime(end_date.strftime("%Y-%m-%d"), '%Y-%m-%d')
else:
ks_date_data["selected_start_date"] = ks_convert_into_utc(start_date, timezone)
ks_date_data["selected_end_date"] = ks_convert_into_utc(end_date, timezone)
return ks_date_data
def ks_get_date_range_from_td_week(timezone, type,self):
ks_date_data = {}
lang = self.env['res.lang']._lang_get(self.env.user.lang)
week_start = lang.week_start
start_Date = rrule.weekday(int(week_start) - 1)
start_date = datetime.today() + relativedelta(weekday=start_Date(-1))
end_date = datetime.now(pytz.timezone(timezone))
start_date = datetime.strptime(start_date.strftime("%Y-%m-%d"), '%Y-%m-%d')
if type == 'date':
ks_date_data["selected_start_date"] = start_date
end_date = datetime.strptime(end_date.strftime("%Y-%m-%d"), '%Y-%m-%d')
ks_date_data["selected_end_date"] = end_date
else:
ks_date_data["selected_start_date"] = ks_convert_into_utc(start_date, timezone)
ks_date_data["selected_end_date"] = ks_convert_into_utc(end_date, timezone)
return ks_date_data
def ks_get_date_range_from_td_quarter(timezone, type,self):
ks_date_data = {}
date = datetime.now(pytz.timezone(timezone))
year = date.year
quarter = int((date.month - 1) / 3) + 1
start_date = datetime(year, 3 * quarter - 2, 1)
end_date = date
if type == 'date':
ks_date_data["selected_start_date"] = datetime.strptime(start_date.strftime("%Y-%m-%d"), '%Y-%m-%d')
ks_date_data["selected_end_date"] = datetime.strptime(end_date.strftime("%Y-%m-%d"), '%Y-%m-%d')
else:
ks_date_data["selected_start_date"] = ks_convert_into_utc(start_date, timezone)
ks_date_data["selected_end_date"] = ks_convert_into_utc(end_date, timezone)
return ks_date_data
# Last Specific Days Ranges : 7, 30, 90, 365
@ -65,18 +131,18 @@ def ks_date_series_l(ks_date_selection, timezone, type, self=None):
# Current Date Ranges : Week, Month, Quarter, year
def ks_date_series_t(ks_date_selection, timezone, type, self=None):
return eval("ks_get_date_range_from_" + ks_date_selection)("current", timezone, type,self)
ks_function_name = globals()["ks_get_date_range_from_" + ks_date_selection]
return ks_function_name("current", timezone, type, self)
# Previous Date Ranges : Week, Month, Quarter, year
def ks_date_series_ls(ks_date_selection, timezone, type,self=None):
return eval("ks_get_date_range_from_" + ks_date_selection)("previous", timezone, type,self)
ks_function_name = globals()["ks_get_date_range_from_" + ks_date_selection]
return ks_function_name("previous", timezone, type, self)
# Next Date Ranges : Day, Week, Month, Quarter, year
def ks_date_series_n(ks_date_selection, timezone, type,self=None):
return eval("ks_get_date_range_from_" + ks_date_selection)("next", timezone, type, self)
ks_function_name = globals()["ks_get_date_range_from_" + ks_date_selection]
return ks_function_name("next", timezone, type, self)
def ks_get_date_range_from_day(date_state, timezone, type,self):
ks_date_data = {}
@ -234,6 +300,17 @@ def ks_get_date_range_from_pastwithout(date_state, self_tz, type,self):
else:
ks_date_data["selected_end_date"] = ks_convert_into_utc(date, self_tz)
return ks_date_data
def ks_get_date_range_from_pastuntil(date_state, self_tz, type, self):
ks_date_data = {}
date = datetime.now(pytz.timezone(self_tz))
date = date - timedelta(days=30)
if type == 'date':
ks_date_data["selected_end_date"] = datetime.strptime(date.strftime("%Y-%m-%d"), '%Y-%m-%d')
else:
ks_date_data["selected_end_date"] = ks_convert_into_utc(date, self_tz)
ks_date_data["selected_start_date"] = False
return ks_date_data
def ks_get_date_range_from_future(date_state, self_tz, type,self):

View File

@ -1,4 +1,4 @@
from . import ks_chart_export
from . import ks_list_export
from . import ks_dashboard_export
from . import ks_dashboard_export
from . import ks_login

View File

@ -27,8 +27,8 @@ class KsListExport(ExportFormat, http.Controller):
'context', 'params')(
params)
list_data = json.loads(list_data)
item = request.env['ks_dashboard_ninja.item'].browse(int(item_id))
if ks_export_boolean:
item = request.env['ks_dashboard_ninja.item'].browse(int(item_id))
ks_timezone = item._context.get('tz') or item.env.user.tz
if not ks_timezone:
ks_tzone = os.environ.get('TZ')

View File

@ -0,0 +1,37 @@
from odoo.http import Controller, request, route
from odoo.addons.web.controllers.main import Home
from odoo import http, tools
class KsHome(Home):
# this is for handle the color and font at login time
@http.route('/web/login', type='http', auth="none")
def web_login(self, redirect=None, **kw):
res = super(KsHome, self).web_login(redirect, **kw)
ksis_enterprise = request.env['res.company'].sudo().ks_check_is_enterprise()
if ksis_enterprise:
template = request.env.ref("ks_dashboard_ninja.ks_dn_load_assets")
template.sudo().write({
'active': False
})
active_template = request.env.ref("ks_dashboard_ninja.ks_dn_load_assets_en")
active_template.sudo().write({
'active': True
})
else:
template = request.env.ref("ks_dashboard_ninja.ks_dn_load_assets_en")
template.sudo().write({
'active': False
})
active_template = request.env.ref("ks_dashboard_ninja.ks_dn_load_assets")
active_template.sudo().write({
'active': True
})
return res

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<record id="config_dn_url" model="ir.config_parameter">
<field name="key">ks_dashboard_ninja.url</field>
<field name="value">https://dn16ai.kappso.in</field>
</record>
</data>
</odoo>

View File

@ -4,9 +4,12 @@ from . import ks_item_action
from . import ks_child_dashboard
from . import ks_dashboard_filters
from . import ks_dashboard_templates
from . import ks_odoo_base
from . import ks_dn_to_do_item
from . import ks_gridstack_per_company
from . import res_settings
from . import ks_ai_ninja_dashboard
from . import ks_ai_whole_dashboard
from . import ks_key_fetch
from . import ks_import_dashboard
from . import ks_load_menu

View File

@ -0,0 +1,216 @@
import json
import logging
import requests
from odoo import http, api, fields, models, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class KsDashboardNInjaAI(models.TransientModel):
_name = 'ks_dashboard_ninja.arti_int'
_description = 'AI Dashboard'
ks_type = fields.Selection([('ks_model', 'Model'), ('ks_keyword', 'Keywords')],
string="Ks AI Type", default='ks_model')
ks_import_model_id = fields.Many2one('ir.model', string='Model',
domain="[('access_ids','!=',False),('transient','=',False),"
"('model','not ilike','base_import%'),('model','not ilike','ir.%'),"
"('model','not ilike','web_editor.%'),('model','not ilike','web_tour.%'),"
"('model','!=','mail.thread'),('model','not ilike','ks_dash%'),('model','not ilike','ks_to%')]",
help="Data source to fetch and read the data for the creation of dashboard items. ")
ks_import_model = fields.Many2one('ir.model', string='Model',
domain="[('access_ids','!=',False),('transient','=',False),"
"('model','not ilike','base_import%'),('model','not ilike','ir.%'),"
"('model','not ilike','web_editor.%'),('model','not ilike','web_tour.%'),"
"('model','!=','mail.thread'),('model','not ilike','ks_dash%'),('model','not ilike','ks_to%')]",
help="Data source to fetch and read the data for the creation of dashboard items. ")
ks_input_keywords = fields.Char("Ks Keywords")
ks_model_show = fields.Boolean(default=False, compute='_compute_show_model')
@api.onchange('ks_input_keywords')
def _compute_show_model(self):
if self.ks_input_keywords and self.ks_type == "ks_keyword":
api_key = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.dn_api_key')
url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url')
if api_key and url:
json_data = {'name': api_key,
'type': self.ks_type,
'keyword': self.ks_input_keywords
}
url = url + "/api/v1/ks_dn_keyword_gen"
ks_response = requests.post(url, data=json_data)
if json.loads(ks_response.text) == False:
self.ks_model_show = True
else:
self.ks_model_show = False
else:
self.ks_model_show = False
@api.model
def ks_get_keywords(self):
url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url')
if url:
url = url + "/api/v1/ks_dn_get_keyword"
ks_response = requests.post(url)
if ks_response.status_code == 200:
return json.loads(ks_response.text)
else:
return []
def ks_do_action(self):
headers = {"Content-Type": "application/json",
"Accept": "application/json",
"Catch-Control": "no-cache",
}
if self.ks_import_model_id:
ks_model_name = self.ks_import_model_id.model
ks_fields = self.env[ks_model_name].fields_get()
ks_filtered_fields = {key: val for key, val in ks_fields.items() if
val['type'] not in ['many2many', 'one2many', 'binary'] and key not in ['id',
'sequence'] and
val['store'] == True}
ks_fields_name = {key: val['type'] for key, val in ks_filtered_fields.items()}
question = ("columns: " + f"{ks_fields_name}")
api_key = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.dn_api_key')
url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url')
if api_key and url:
json_data = {'name': api_key,
'question': question,
'url': self.env['ir.config_parameter'].sudo().get_param('web.base.url'),
'db_name': self.env.cr.dbname
}
url = url + "/api/v1/ks_dn_main_api"
ks_ai_response = requests.post(url, data=json_data)
if ks_ai_response.status_code == 200:
ks_ai_response = json.loads(ks_ai_response.text)
# create dummy dash to create items on the dashboard, later deleted it.
ks_create_record = self.env['ks_dashboard_ninja.board'].create({
'name': 'AI dashboard',
'ks_dashboard_menu_name': 'AI menu',
'ks_dashboard_default_template': self.env.ref('ks_dashboard_ninja.ks_blank', False).id,
'ks_dashboard_top_menu_id': self.env['ir.ui.menu'].search([('name', '=', 'My Dashboard')])[
0].id,
})
ks_dash_id = ks_create_record.id
ks_result = self.env['ks_dashboard_ninja.item'].create_ai_dash(ks_ai_response, ks_dash_id,
ks_model_name)
context = {'ks_dash_id': self._context['ks_dashboard_id'],
'ks_dash_name': self.env['ks_dashboard_ninja.board'].search([
('id', '=', self._context['ks_dashboard_id'])]).name,
'ks_delete_dash_id': ks_dash_id}
# return client action created through js for AI dashboard to render items on dummy dashboard
if (ks_result == "success"):
return {
'type': 'ir.actions.client',
'name': context['ks_dash_name'],
'params': {'ks_dashboard_id': ks_create_record.id},
'tag': 'ks_dashboard_ninja_ai',
'target': 'new',
'context': context
}
else:
self.env['ks_dashboard_ninja.board'].browse(ks_dash_id).unlink()
raise ValidationError(
_("Items didn't render because AI provides invalid response for this model.Please try again"))
else:
raise ValidationError(_("AI Responds with the following status:- %s") % ks_ai_response.text)
else:
raise ValidationError(_("Please enter URL and API Key in General Settings"))
else:
raise ValidationError(_("Please enter the Model"))
def ks_generate_item(self):
if self.ks_input_keywords:
api_key = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.dn_api_key')
url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url')
if api_key and url:
json_data = {'name': api_key,
'type': self.ks_type,
'keyword': self.ks_input_keywords
}
url = url + "/api/v1/ks_dn_keyword_gen"
ks_response = requests.post(url, data=json_data)
else:
raise ValidationError(_("Please put API key and URL"))
if json.loads(ks_response.text) != False and ks_response.status_code == 200:
ks_ai_response = json.loads(ks_response.text)
ks_dash_id = self._context['ks_dashboard_id']
ks_model_name = ks_ai_response[0]['model']
ks_result = self.env['ks_dashboard_ninja.item'].create_ai_dash(ks_ai_response, ks_dash_id,
ks_model_name)
if ks_result == "success":
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
else:
raise ValidationError(_("Items didn't render, please try again!"))
else:
ks_model_name = self.ks_import_model.model
ks_fields = self.env[ks_model_name].fields_get()
ks_filtered_fields = {key: val for key, val in ks_fields.items() if
val['type'] not in ['many2many', 'one2many', 'binary'] and key not in ['id',
'sequence'] and
val['store'] == True}
ks_fields_name = {key: val['type'] for key, val in ks_filtered_fields.items()}
question = ("schema: " + f"{ks_fields_name}")
model = ("model:" + f"{ks_model_name}")
api_key = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.dn_api_key')
url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url')
if api_key and url:
json_data = {'name': api_key,
'question': self.ks_input_keywords,
'type': self.ks_type,
'schema': question,
'model': model,
'url': self.env['ir.config_parameter'].sudo().get_param('web.base.url'),
'db_name': self.env.cr.dbname
}
url = url + "/api/v1/ks_dn_main_api"
ks_ai_response = requests.post(url, data=json_data)
if ks_ai_response.status_code == 200:
ks_ai_response = json.loads(ks_ai_response.text)
ks_dash_id = self._context['ks_dashboard_id']
ks_model_name = (ks_ai_response[0]['model']).lower()
if self.env['ir.model'].search([('model', '=', ks_model_name)]).id or self.env[
'ir.model'].search([('name', '=', ks_model_name)]).id:
if self.env['ir.model'].search([('name', '=', ks_model_name)]).id:
ks_model_name = self.env['ir.model'].search([('name', '=', ks_model_name)]).model
else:
ks_model_name = (ks_ai_response[0]['model']).lower()
ks_result = self.env['ks_dashboard_ninja.item'].create_ai_dash(ks_ai_response, ks_dash_id,
ks_model_name)
if ks_result == "success":
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
else:
raise ValidationError(_("Items didn't render, please try again!"))
else:
raise ValidationError(_("%s model does not exist.Please install") % ks_model_name)
else:
raise ValidationError(
_("AI Responds with the following status:- %s") % ks_ai_response.text)
else:
raise ValidationError(_("Please enter URL and API Key in General Settings"))
else:
raise ValidationError(_("Enter the input keywords to render the item"))

View File

@ -0,0 +1,87 @@
import json
import logging
import requests
from odoo import http, api, fields, models, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class KsAIDashboardninja(models.TransientModel):
_name = 'ks_dashboard_ninja.ai_dashboard'
_description = 'AI Dashboard'
ks_import_model_id = fields.Many2one('ir.model', string='Model',
domain="[('access_ids','!=',False),('transient','=',False),"
"('model','not ilike','base_import%'),('model','not ilike','ir.%'),"
"('model','not ilike','web_editor.%'),('model','not ilike','web_tour.%'),"
"('model','!=','mail.thread'),('model','not ilike','ks_dash%'),('model','not ilike','ks_to%')]",
help="Data source to fetch and read the data for the creation of dashboard items. ", required=True)
ks_dash_name = fields.Char(string="Dashboard Name", required=True)
ks_menu_name = fields.Char(string="Menu Name", required=True)
ks_top_menu_id = fields.Many2one('ir.ui.menu',
domain="[('parent_id','=',False)]",
string="Show Under Menu", required=True,
default=lambda self: self.env['ir.ui.menu'].search(
[('name', '=', 'My Dashboard')])[0])
ks_template = fields.Many2one('ks_dashboard_ninja.board_template',
default=lambda self: self.env.ref('ks_dashboard_ninja.ks_blank',
False),
string="Dashboard Template")
def ks_do_action(self):
headers = {"Content-Type": "application/json",
"Accept": "application/json",
"Catch-Control": "no-cache",
}
if self.ks_import_model_id:
ks_model_name = self.ks_import_model_id.model
ks_fields = self.env[ks_model_name].fields_get()
ks_filtered_fields = {key: val for key, val in ks_fields.items() if val['type'] not in ['many2many', 'one2many', 'binary'] and key not in ['id','sequence'] and val['store'] == True}
ks_fields_name = {key:val['type'] for key , val in ks_filtered_fields.items()}
question = ("columns: "+ f"{ks_fields_name}")
api_key = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.dn_api_key')
url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url')
if api_key and url:
json_data = {'name': api_key,
'question':question,
'url':self.env['ir.config_parameter'].sudo().get_param('web.base.url'),
'db_name':self.env.cr.dbname
}
url = url+"/api/v1/ks_dn_main_api"
ks_ai_response = requests.post(url, data=json_data)
if ks_ai_response.status_code == 200:
ks_ai_response = json.loads(ks_ai_response.text)
ks_create_record = self.env['ks_dashboard_ninja.board'].create({
'name': self.ks_dash_name,
'ks_dashboard_menu_name': self.ks_menu_name,
'ks_dashboard_default_template': self.ks_template.id,
'ks_dashboard_top_menu_id': self.ks_top_menu_id.id,
})
ks_dash_id = ks_create_record.id
ks_result = self.env['ks_dashboard_ninja.item'].create_ai_dash(ks_ai_response, ks_dash_id,
ks_model_name)
if (ks_result == "success"):
return {
'type': 'ir.actions.client',
'name': "Dashboard Ninja",
'res_model': 'ks_dashboard_ninja.board',
'params': {'ks_dashboard_id': ks_dash_id},
'tag': 'ks_dashboard_ninja',
}
else:
self.env['ks_dashboard_ninja.board'].browse(ks_dash_id).unlink()
raise ValidationError(_("Items didn't render, please try again!"))
else:
raise ValidationError(_("AI Responds with the following status:- %s") % ks_ai_response.text)
else:
raise ValidationError(_("Please enter URL and API Key in General Settings"))
else:
raise ValidationError(_("Please enter the Model"))

View File

@ -11,7 +11,7 @@ class KsDashboardNinjaTemplate(models.Model):
ks_dashboard_board_id = fields.Many2one('ks_dashboard_ninja.board', string="Dashboard")
ks_model_id = fields.Many2one('ir.model', string='Model',
domain="[('access_ids','!=',False),('transient','=',False),"
"('model','not ilike','base_import%'),('model','not ilike','ir.%'),"
"('model','not ilike','base_import%'),'|',('model','not ilike','ir.%'),('model','=ilike','_%ir.%'),"
"('model','not ilike','web_editor.%'),('model','not ilike','web_tour.%'),"
"('model','!=','mail.thread'),('model','not ilike','ks_dash%'), ('model','not ilike','ks_to%')]",
help="Data source to fetch and read the data for the creation of dashboard items. ")
@ -54,7 +54,7 @@ class KsDashboardNinjaTemplate(models.Model):
ks_dashboard_board_id = fields.Many2one('ks_dashboard_ninja.board', string="Dashboard")
ks_model_id = fields.Many2one('ir.model', string='Model',
domain="[('access_ids','!=',False),('transient','=',False),"
"('model','not ilike','base_import%'),('model','not ilike','ir.%'),"
"('model','not ilike','base_import%'),'|',('model','not ilike','ir.%'),('model','=ilike','_%ir.%'),"
"('model','not ilike','web_editor.%'),('model','not ilike','web_tour.%'),"
"('model','!=','mail.thread'),('model','not ilike','ks_dash%'), ('model','not ilike','ks_to%')]",
help="Data source to fetch and read the data for the creation of dashboard items. ")

View File

@ -41,6 +41,10 @@ class KsDashboardNinjaBoard(models.Model):
('t_month', 'This Month'),
('t_quarter', 'This Quarter'),
('t_year', 'This Year'),
('td_week', 'Week to Date'),
('td_month', 'Month to Date'),
('td_quarter', 'Quarter to Date'),
('td_year', 'Year to Date'),
('n_day', 'Next Day'),
('n_week', 'Next Week'),
('n_month', 'Next Month'),
@ -56,6 +60,7 @@ class KsDashboardNinjaBoard(models.Model):
('l_quarter', 'Last 90 days'),
('l_year', 'Last 365 days'),
('ls_past_until_now', 'Past Till Now'),
('ls_pastuntil_lastmonth', 'Past Till: 30 days ago'),
('ls_pastwithout_now', ' Past Excluding Today'),
('n_future_starting_now', 'Future Starting Now'),
('n_futurestarting_tomorrow', 'Future Starting Tomorrow'),
@ -178,6 +183,8 @@ class KsDashboardNinjaBoard(models.Model):
if 'ks_dashboard_menu_sequence' in vals:
rec.ks_dashboard_menu_id.sudo().sequence = vals['ks_dashboard_menu_sequence']
if 'name' in vals:
self.ks_dashboard_client_action_id.name = vals['name']
return record
@ -187,6 +194,7 @@ class KsDashboardNinjaBoard(models.Model):
else:
for rec in self:
rec.ks_dashboard_client_action_id.sudo().unlink()
rec.ks_child_dashboard_ids.unlink()
rec.ks_dashboard_menu_id.sudo().unlink()
rec.ks_dashboard_items_ids.unlink()
res = super(KsDashboardNinjaBoard, self).unlink()
@ -257,14 +265,14 @@ class KsDashboardNinjaBoard(models.Model):
}
default_grid_id = ks_dashboard_rec.ks_get_grid_config()
dashboard_data['ks_gridstack_config'] = default_grid_id.ks_gridstack_config
dashboard_data['ks_gridstack_config_id'] = default_grid_id.id
dashboard_data['ks_gridstack_config'] = default_grid_id[0].ks_gridstack_config
dashboard_data['ks_gridstack_config_id'] = default_grid_id[0].id
if self.env['ks_dashboard_ninja.child_board'].search(
[['id', 'in', ks_dashboard_rec.ks_child_dashboard_ids.ids], ['company_id', '=', self.env.company.id],
['board_type', '!=', 'default']], limit=1):
dashboard_data['ks_child_boards'] = {
'ks_default': [ks_dashboard_rec.name, default_grid_id.ks_gridstack_config]}
'ks_default': [ks_dashboard_rec.name, default_grid_id[0].ks_gridstack_config]}
selecred_rec = self.env['ks_dashboard_ninja.child_board'].search(
[['id', 'in', ks_dashboard_rec.ks_child_dashboard_ids.ids], ['ks_active', '=', True],
['company_id', '=', self.env.company.id], ['board_type', '!=', 'default']], limit=1)
@ -326,27 +334,31 @@ class KsDashboardNinjaBoard(models.Model):
if rec.ks_actions:
context = {}
try:
context = eval(rec.ks_actions.context)
context = safe_eval(rec.ks_actions.context)
except Exception:
context = {}
action['name'] = rec.ks_actions.name
action['type'] = rec.ks_actions.type
action['res_model'] = rec.ks_actions.res_model
action['views'] = rec.ks_actions.views
action['view_mode'] = rec.ks_actions.view_mode
action['search_view_id'] = rec.ks_actions.search_view_id.id
# Managing those views that have the access rights
ks_actions = rec.ks_actions.sudo()
action['name'] = ks_actions.name
action['type'] = ks_actions.type
action['res_model'] = ks_actions.res_model
action['views'] = ks_actions.views
action['view_mode'] = ks_actions.view_mode
action['search_view_id'] = ks_actions.search_view_id.id
action['context'] = context
action['target'] = 'current'
elif rec.ks_is_client_action and rec.ks_client_action:
clint_action = {}
clint_action['name'] = rec.ks_client_action.name
clint_action['type'] = rec.ks_client_action.type
clint_action['res_model'] = rec.ks_client_action.res_model
clint_action['xml_id'] = rec.ks_client_action.xml_id
clint_action['tag'] = rec.ks_client_action.tag
clint_action['binding_type'] = rec.ks_client_action.binding_type
clint_action['params'] = rec.ks_client_action.params
ks_client_action = rec.ks_client_action.sudo()
clint_action['name'] = ks_client_action.name
clint_action['type'] = ks_client_action.type
clint_action['res_model'] = ks_client_action.res_model
clint_action['xml_id'] = ks_client_action.xml_id
clint_action['tag'] = ks_client_action.tag
clint_action['binding_type'] = ks_client_action.binding_type
clint_action['params'] = ks_client_action.params
clint_action['target'] = 'current'
action = clint_action,
@ -386,8 +398,12 @@ class KsDashboardNinjaBoard(models.Model):
'ks_dashboard_item_type': rec.ks_dashboard_item_type,
'ks_chart_item_color': rec.ks_chart_item_color,
'ks_chart_groupby_type': rec.ks_chart_groupby_type,
'ks_chart_measure_field': rec.ks_chart_measure_field.ids,
'ks_chart_measure_field_2': rec.ks_chart_measure_field_2.ids,
'ks_chart_relation_groupby': rec.ks_chart_relation_groupby.id,
'ks_chart_relation_groupby_name': rec.ks_chart_relation_groupby.name,
'ks_chart_relation_sub_groupby': rec.ks_chart_relation_sub_groupby.id,
'ks_chart_relation_sub_groupby_name': rec.ks_chart_relation_sub_groupby.name,
'ks_chart_date_groupby': rec.ks_chart_date_groupby,
'ks_record_field': rec.ks_record_field.id if rec.ks_record_field else False,
'ks_chart_data': rec._ks_get_chart_data(item_domain1),
@ -406,8 +422,8 @@ class KsDashboardNinjaBoard(models.Model):
'ks_target_view': rec.ks_target_view,
'ks_date_filter_selection': rec.ks_date_filter_selection,
'ks_show_data_value': rec.ks_show_data_value,
'ks_update_items_data': rec.ks_update_items_data,
'ks_show_records': rec.ks_show_records,
'ks_tile_data':rec._ksGettileData(item_domain1),
# 'action_id': rec.ks_actions.id if rec.ks_actions else False,
'sequence': 0,
'max_sequnce': len(rec.ks_action_lines) if rec.ks_action_lines else False,
@ -416,21 +432,30 @@ class KsDashboardNinjaBoard(models.Model):
'ks_data_calculation_type': rec.ks_data_calculation_type,
'ks_export_all_records': rec.ks_export_all_records,
'ks_data_formatting': rec.ks_data_format,
'ks_auto_update_type': rec.ks_auto_update_type,
'ks_show_live_pop_up': rec.ks_show_live_pop_up,
'ks_is_client_action': rec.ks_is_client_action,
'ks_pagination_limit': rec.ks_pagination_limit,
'ks_record_data_limit': rec.ks_record_data_limit,
'ks_chart_cumulative_field': rec.ks_chart_cumulative_field.ids,
'ks_chart_cumulative': rec.ks_chart_cumulative,
'ks_chart_is_cumulative': rec.ks_chart_is_cumulative,
'ks_button_color': rec.ks_button_color,
'ks_to_do_data': rec._ksGetToDOData(),
'ks_multiplier_active': rec.ks_multiplier_active,
'ks_multiplier': rec.ks_multiplier,
'ks_unit': rec.ks_unit,
'ks_unit_selection':rec.ks_unit_selection,
'ks_chart_unit':rec.ks_chart_unit,
'ks_currency_id':rec.ks_currency_id,
'ks_goal_liness': True if rec.ks_goal_lines else False,
'ks_currency_symbol': ks_currency_symbol,
'ks_currency_position': ks_currency_position,
'ks_precision_digits': ks_precision_digits if ks_precision_digits else 2
'ks_precision_digits': ks_precision_digits if ks_precision_digits else 2,
'ks_data_label_type': rec.ks_data_label_type,
'ks_as_of_now': rec.ks_as_of_now,
'ks_info': rec.ks_info,
'ks_company': rec.ks_company_id.name if rec.ks_company_id else False,
}
return item
@ -587,14 +612,18 @@ class KsDashboardNinjaBoard(models.Model):
[['id', 'in', rec.ks_dashboard_ninja_board_id.ks_child_dashboard_ids.ids], ['ks_active', '=', True],
['company_id', '=', self.env.company.id]], limit=1)
default_grid_id = rec.ks_dashboard_ninja_board_id.ks_get_grid_config()
keys_data = {}
if rec.ks_dashboard_ninja_board_id.ks_gridstack_config:
keys_data = json.loads(rec.ks_dashboard_ninja_board_id.ks_gridstack_config)
elif selecred_rec:
keys_data = json.loads(selecred_rec.ks_gridstack_config)
elif rec.ks_dashboard_ninja_board_id.ks_child_dashboard_ids[0].ks_gridstack_config:
keys_data = json.loads(rec.ks_dashboard_ninja_board_id.ks_child_dashboard_ids[0].ks_gridstack_config)
elif self._context.get('gridstack_config',False):
keys_data = self._context.get('gridstack_config',False)
else:
keys_data = {rec.id: json.loads(rec.grid_corners.replace("\'", "\""))}
if rec.grid_corners:
keys_data = {rec.id: json.loads(rec.grid_corners.replace("\'", "\""))}
keys_list = keys_data.keys()
grid_corners = {}
if val in keys_list:
@ -662,9 +691,6 @@ class KsDashboardNinjaBoard(models.Model):
'ks_year_period_2': rec.ks_year_period_2,
'ks_domain_2': rec.ks_domain_2,
'ks_show_data_value': rec.ks_show_data_value,
'ks_auto_update_type': rec.ks_auto_update_type,
'ks_show_live_pop_up': rec.ks_show_live_pop_up,
'ks_update_items_data': rec.ks_update_items_data,
'ks_list_target_deviation_field': rec.ks_list_target_deviation_field.name,
'ks_unit': rec.ks_unit,
'ks_show_records': rec.ks_show_records,
@ -673,6 +699,7 @@ class KsDashboardNinjaBoard(models.Model):
'ks_domain_extension': rec.ks_domain_extension,
'ks_unit_selection': rec.ks_unit_selection,
'ks_chart_unit': rec.ks_chart_unit,
'ks_currency_id':rec.ks_currency_id.id,
'ks_bar_chart_stacked': rec.ks_bar_chart_stacked,
'ks_goal_bar_line': rec.ks_goal_bar_line,
'ks_actions': rec.ks_actions.xml_id if rec.ks_actions else False,
@ -690,6 +717,9 @@ class KsDashboardNinjaBoard(models.Model):
'ks_multiplier_active': rec.ks_multiplier_active,
'ks_multiplier': rec.ks_multiplier,
'ks_multiplier_lines': ks_multiplier_lines if ks_multiplier_lines else False,
'ks_data_label_type': rec.ks_data_label_type,
'ks_as_of_now': rec.ks_as_of_now,
# 'ks_info':rec.ks_info,
}
if grid_corners:
item.update({
@ -759,23 +789,25 @@ class KsDashboardNinjaBoard(models.Model):
[['id', 'in', dash.ks_child_dashboard_ids.ids], ['ks_active', '=', True],
['company_id', '=', self.env.company.id]], limit=1)
ks_dashboard_rec = self.browse(ks_dashboard_id)
dashboard_data = {
'name': ks_dashboard_rec.name,
'ks_dashboard_menu_name': ks_dashboard_rec.ks_dashboard_menu_name,
'ks_gridstack_config': ks_dashboard_rec.ks_gridstack_config,
'ks_set_interval': ks_dashboard_rec.ks_set_interval,
'ks_date_filter_selection': ks_dashboard_rec.ks_date_filter_selection,
'ks_dashboard_start_date': ks_dashboard_rec.ks_dashboard_start_date,
'ks_dashboard_end_date': ks_dashboard_rec.ks_dashboard_end_date,
'ks_dashboard_top_menu_id': ks_dashboard_rec.ks_dashboard_top_menu_id.id,
'ks_data_formatting': ks_dashboard_rec.ks_data_formatting,
}
if selecred_rec:
name = selecred_rec.name
grid_conf = selecred_rec.ks_gridstack_config
elif dash.ks_child_dashboard_ids:
name = dash.display_name
grid_conf = dash.ks_child_dashboard_ids[0].ks_gridstack_config
else:
name = dash.name
grid_conf = dash.ks_gridstack_config
dashboard_data = self.ks_prepare_export_data_vals(ks_dashboard_rec, grid_conf=grid_conf)
if selecred_rec:
dashboard_data['name'] = selecred_rec.name
dashboard_data['ks_gridstack_config'] = selecred_rec.ks_gridstack_config
elif len(ks_dashboard_rec.ks_child_dashboard_ids) == 1:
dashboard_data['name'] = ks_dashboard_rec.ks_child_dashboard_ids.name
dashboard_data['ks_gridstack_config'] = ks_dashboard_rec.ks_child_dashboard_ids.ks_gridstack_config
elif len(ks_dashboard_rec.ks_child_dashboard_ids) > 1:
dashboard_data['name'] = ks_dashboard_rec.ks_child_dashboard_ids[0].name
dashboard_data['ks_gridstack_config'] = ks_dashboard_rec.ks_child_dashboard_ids[0].ks_gridstack_config
if dashboard_data['name'] != 'Default Board Layout':
dashboard_data['ks_dashboard_menu_name'] = selecred_rec.name
if dashboard_data['name'] == 'Default Board Layout':
@ -789,7 +821,6 @@ class KsDashboardNinjaBoard(models.Model):
items.append(item)
dashboard_data['ks_item_data'] = items
ks_dashboard_data.append(dashboard_data)
ks_dashboard_export_data = {
@ -798,6 +829,20 @@ class KsDashboardNinjaBoard(models.Model):
}
return ks_dashboard_export_data
def ks_prepare_export_data_vals(self, ks_dashboard_rec, grid_conf=None,):
dashboard_data = {
'name': ks_dashboard_rec.name,
'ks_dashboard_menu_name': ks_dashboard_rec.ks_dashboard_menu_name,
'ks_gridstack_config': grid_conf if grid_conf else '{}',
'ks_set_interval': ks_dashboard_rec.ks_set_interval,
'ks_date_filter_selection': ks_dashboard_rec.ks_date_filter_selection,
'ks_dashboard_start_date': ks_dashboard_rec.ks_dashboard_start_date,
'ks_dashboard_end_date': ks_dashboard_rec.ks_dashboard_end_date,
'ks_dashboard_top_menu_id': ks_dashboard_rec.ks_dashboard_top_menu_id.id,
'ks_data_formatting': ks_dashboard_rec.ks_data_formatting,
}
return dashboard_data
@api.model
def ks_import_dashboard(self, file, menu_id):
try:
@ -828,25 +873,13 @@ class KsDashboardNinjaBoard(models.Model):
ks_dashboard_top_menu_id = self.env['ir.ui.menu'].browse(ks_dashboard_top_menu_id)
except Exception:
ks_dashboard_top_menu_id = False
vals = {
'name': data['name'],
'ks_dashboard_menu_name': data['ks_dashboard_menu_name'],
'ks_dashboard_top_menu_id': menu_id.id if menu_id else self.env.ref(
"ks_dashboard_ninja.board_menu_root").id,
'ks_dashboard_active': True,
'ks_gridstack_config': data['ks_gridstack_config'],
'ks_dashboard_default_template': self.env.ref("ks_dashboard_ninja.ks_blank").id,
'ks_dashboard_group_access': False,
'ks_set_interval': data['ks_set_interval'],
'ks_date_filter_selection': data['ks_date_filter_selection'],
'ks_dashboard_start_date': data['ks_dashboard_start_date'],
'ks_dashboard_end_date': data['ks_dashboard_end_date'],
}
vals = self.ks_prepare_import_data_vals(data, menu_id)
# Creating Dashboard
dashboard_id = self.create(vals)
if data['ks_gridstack_config']:
ks_gridstack_config = eval(data['ks_gridstack_config'])
ks_gridstack_config = safe_eval(data['ks_gridstack_config'])
ks_grid_stack_config = {}
item_ids = []
@ -898,6 +931,23 @@ class KsDashboardNinjaBoard(models.Model):
return "Success"
# separate function to make item for import
def ks_prepare_import_data_vals(self, data, menu_id):
vals = {
'name': data['name'],
'ks_dashboard_menu_name': data['ks_dashboard_menu_name'],
'ks_dashboard_top_menu_id': menu_id.id if menu_id else self.env.ref(
"ks_dashboard_ninja.board_menu_root").id,
'ks_dashboard_active': True,
'ks_gridstack_config': data['ks_gridstack_config'],
'ks_dashboard_default_template': self.env.ref("ks_dashboard_ninja.ks_blank").id,
'ks_dashboard_group_access': False,
'ks_set_interval': data['ks_set_interval'],
'ks_date_filter_selection': data['ks_date_filter_selection'],
'ks_dashboard_start_date': data['ks_dashboard_start_date'],
'ks_dashboard_end_date': data['ks_dashboard_end_date'],
}
return vals
def ks_create_item(self, item):
model = self.env['ir.model'].search([('model', '=', item['ks_model_id'])])
@ -982,149 +1032,155 @@ class KsDashboardNinjaBoard(models.Model):
return ks_item
def ks_prepare_item(self, item):
ks_measure_field_ids = []
ks_measure_field_2_ids = []
try:
ks_measure_field_ids = []
ks_measure_field_2_ids = []
for ks_measure in item['ks_chart_measure_field']:
ks_measure_id = self.env['ir.model.fields'].search(
[('name', '=', ks_measure), ('model', '=', item['ks_model_id'])])
if ks_measure_id:
ks_measure_field_ids.append(ks_measure_id.id)
item['ks_chart_measure_field'] = [(6, 0, ks_measure_field_ids)]
for ks_measure in item['ks_chart_measure_field']:
ks_measure_id = self.env['ir.model.fields'].search(
[('name', '=', ks_measure), ('model', '=', item['ks_model_id'])])
if ks_measure_id:
ks_measure_field_ids.append(ks_measure_id.id)
item['ks_chart_measure_field'] = [(6, 0, ks_measure_field_ids)]
for ks_measure in item['ks_chart_measure_field_2']:
ks_measure_id = self.env['ir.model.fields'].search(
[('name', '=', ks_measure), ('model', '=', item['ks_model_id'])])
if ks_measure_id:
ks_measure_field_2_ids.append(ks_measure_id.id)
item['ks_chart_measure_field_2'] = [(6, 0, ks_measure_field_2_ids)]
for ks_measure in item['ks_chart_measure_field_2']:
ks_measure_id = self.env['ir.model.fields'].search(
[('name', '=', ks_measure), ('model', '=', item['ks_model_id'])])
if ks_measure_id:
ks_measure_field_2_ids.append(ks_measure_id.id)
item['ks_chart_measure_field_2'] = [(6, 0, ks_measure_field_2_ids)]
ks_list_view_group_fields = []
for ks_measure in item['ks_list_view_group_fields']:
ks_measure_id = self.env['ir.model.fields'].search(
[('name', '=', ks_measure), ('model', '=', item['ks_model_id'])])
ks_list_view_group_fields = []
for ks_measure in item['ks_list_view_group_fields']:
ks_measure_id = self.env['ir.model.fields'].search(
[('name', '=', ks_measure), ('model', '=', item['ks_model_id'])])
if ks_measure_id:
ks_list_view_group_fields.append(ks_measure_id.id)
item['ks_list_view_group_fields'] = [(6, 0, ks_list_view_group_fields)]
if ks_measure_id:
ks_list_view_group_fields.append(ks_measure_id.id)
item['ks_list_view_group_fields'] = [(6, 0, ks_list_view_group_fields)]
ks_list_view_field_ids = []
for ks_list_field in item['ks_list_view_fields']:
ks_list_field_id = self.env['ir.model.fields'].search(
[('name', '=', ks_list_field), ('model', '=', item['ks_model_id'])])
if ks_list_field_id:
ks_list_view_field_ids.append(ks_list_field_id.id)
item['ks_list_view_fields'] = [(6, 0, ks_list_view_field_ids)]
ks_list_view_field_ids = []
for ks_list_field in item['ks_list_view_fields']:
ks_list_field_id = self.env['ir.model.fields'].search(
[('name', '=', ks_list_field), ('model', '=', item['ks_model_id'])])
if ks_list_field_id:
ks_list_view_field_ids.append(ks_list_field_id.id)
item['ks_list_view_fields'] = [(6, 0, ks_list_view_field_ids)]
if item['ks_record_field']:
ks_record_field = item['ks_record_field']
ks_record_id = self.env['ir.model.fields'].search(
[('name', '=', ks_record_field), ('model', '=', item['ks_model_id'])])
if ks_record_id:
item['ks_record_field'] = ks_record_id.id
else:
item['ks_record_field'] = False
if item['ks_record_field']:
ks_record_field = item['ks_record_field']
ks_record_id = self.env['ir.model.fields'].search(
[('name', '=', ks_record_field), ('model', '=', item['ks_model_id'])])
if ks_record_id:
item['ks_record_field'] = ks_record_id.id
else:
item['ks_record_field'] = False
if item['ks_date_filter_field']:
ks_date_filter_field = item['ks_date_filter_field']
ks_record_id = self.env['ir.model.fields'].search(
[('name', '=', ks_date_filter_field), ('model', '=', item['ks_model_id'])])
if ks_record_id:
item['ks_date_filter_field'] = ks_record_id.id
else:
item['ks_date_filter_field'] = False
if item['ks_date_filter_field']:
ks_date_filter_field = item['ks_date_filter_field']
ks_record_id = self.env['ir.model.fields'].search(
[('name', '=', ks_date_filter_field), ('model', '=', item['ks_model_id'])])
if ks_record_id:
item['ks_date_filter_field'] = ks_record_id.id
else:
item['ks_date_filter_field'] = False
if item['ks_chart_relation_groupby']:
ks_group_by = item['ks_chart_relation_groupby']
ks_record_id = self.env['ir.model.fields'].search(
[('name', '=', ks_group_by), ('model', '=', item['ks_model_id'])])
if ks_record_id:
item['ks_chart_relation_groupby'] = ks_record_id.id
else:
item['ks_chart_relation_groupby'] = False
if item['ks_chart_relation_groupby']:
ks_group_by = item['ks_chart_relation_groupby']
ks_record_id = self.env['ir.model.fields'].search(
[('name', '=', ks_group_by), ('model', '=', item['ks_model_id'])])
if ks_record_id:
item['ks_chart_relation_groupby'] = ks_record_id.id
else:
item['ks_chart_relation_groupby'] = False
if item['ks_chart_relation_sub_groupby']:
ks_group_by = item['ks_chart_relation_sub_groupby']
ks_chart_relation_sub_groupby = self.env['ir.model.fields'].search(
[('name', '=', ks_group_by), ('model', '=', item['ks_model_id'])])
if ks_chart_relation_sub_groupby:
item['ks_chart_relation_sub_groupby'] = ks_chart_relation_sub_groupby.id
else:
item['ks_chart_relation_sub_groupby'] = False
if item['ks_chart_relation_sub_groupby']:
ks_group_by = item['ks_chart_relation_sub_groupby']
ks_chart_relation_sub_groupby = self.env['ir.model.fields'].search(
[('name', '=', ks_group_by), ('model', '=', item['ks_model_id'])])
if ks_chart_relation_sub_groupby:
item['ks_chart_relation_sub_groupby'] = ks_chart_relation_sub_groupby.id
else:
item['ks_chart_relation_sub_groupby'] = False
# Sort by field : Many2one Entery
if item['ks_sort_by_field']:
ks_group_by = item['ks_sort_by_field']
ks_sort_by_field = self.env['ir.model.fields'].search(
[('name', '=', ks_group_by), ('model', '=', item['ks_model_id'])])
if ks_sort_by_field:
item['ks_sort_by_field'] = ks_sort_by_field.id
else:
item['ks_sort_by_field'] = False
if item['ks_list_target_deviation_field']:
ks_list_target_deviation_field = item['ks_list_target_deviation_field']
record_id = self.env['ir.model.fields'].search(
[('name', '=', ks_list_target_deviation_field), ('model', '=', item['ks_model_id'])])
if record_id:
item['ks_list_target_deviation_field'] = record_id.id
else:
item['ks_list_target_deviation_field'] = False
ks_model_id = self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).id
if item.get("ks_actions"):
ks_action = self.env.ref(item["ks_actions"], False)
if ks_action:
item["ks_actions"] = ks_action.id
else:
item["ks_actions"] = False
if item.get("ks_client_action"):
ks_action = self.env.ref(item["ks_client_action"], False)
if ks_action:
item["ks_client_action"] = ks_action.id
else:
item["ks_client_action"] = False
if (item['ks_model_id_2']):
ks_model_2 = item['ks_model_id_2'].replace(".", "_")
ks_model_id_2 = self.env['ir.model'].search([('model', '=', item['ks_model_id_2'])]).id
if item['ks_record_field_2']:
ks_record_field = item['ks_record_field_2']
ks_record_id = self.env['ir.model.fields'].search(
[('model', '=', item['ks_model_id_2']), ('name', '=', ks_record_field)])
if ks_record_id:
item['ks_record_field_2'] = ks_record_id.id
if item['ks_sort_by_field']:
ks_group_by = item['ks_sort_by_field']
ks_sort_by_field = self.env['ir.model.fields'].search(
[('name', '=', ks_group_by), ('model', '=', item['ks_model_id'])])
if ks_sort_by_field:
item['ks_sort_by_field'] = ks_sort_by_field.id
else:
item['ks_record_field_2'] = False
if item['ks_date_filter_field_2']:
ks_record_id = self.env['ir.model.fields'].search(
[('model', '=', item['ks_model_id_2']), ('name', '=', item['ks_date_filter_field_2'])])
item['ks_sort_by_field'] = False
if ks_record_id:
item['ks_date_filter_field_2'] = ks_record_id.id
if item['ks_list_target_deviation_field']:
ks_list_target_deviation_field = item['ks_list_target_deviation_field']
record_id = self.env['ir.model.fields'].search(
[('name', '=', ks_list_target_deviation_field), ('model', '=', item['ks_model_id'])])
if record_id:
item['ks_list_target_deviation_field'] = record_id.id
else:
item['ks_date_filter_field_2'] = False
item['ks_list_target_deviation_field'] = False
item['ks_model_id_2'] = ks_model_id_2
else:
item['ks_date_filter_field_2'] = False
item['ks_record_field_2'] = False
ks_model_id = self.env['ir.model'].search([('model', '=', item['ks_model_id'])]).id
item['ks_model_id'] = ks_model_id
if item.get("ks_actions"):
ks_action = self.env.ref(item["ks_actions"], False)
if ks_action:
item["ks_actions"] = ks_action.id
else:
item["ks_actions"] = False
if item.get("ks_client_action"):
ks_action = self.env.ref(item["ks_client_action"], False)
if ks_action:
item["ks_client_action"] = ks_action.id
else:
item["ks_client_action"] = False
item['ks_goal_liness'] = False
item['ks_item_start_date'] = item['ks_item_start_date'] if \
item['ks_item_start_date'] else False
item['ks_item_end_date'] = item['ks_item_end_date'] if \
item['ks_item_end_date'] else False
item['ks_item_start_date_2'] = item['ks_item_start_date_2'] if \
item['ks_item_start_date_2'] else False
item['ks_item_end_date_2'] = item['ks_item_end_date_2'] if \
item['ks_item_end_date_2'] else False
if (item['ks_model_id_2']):
ks_model_2 = item['ks_model_id_2'].replace(".", "_")
ks_model_id_2 = self.env['ir.model'].search([('model', '=', item['ks_model_id_2'])]).id
if item['ks_record_field_2']:
ks_record_field = item['ks_record_field_2']
ks_record_id = self.env['ir.model.fields'].search(
[('model', '=', item['ks_model_id_2']), ('name', '=', ks_record_field)])
return item
if ks_record_id:
item['ks_record_field_2'] = ks_record_id.id
else:
item['ks_record_field_2'] = False
if item['ks_date_filter_field_2']:
ks_record_id = self.env['ir.model.fields'].search(
[('model', '=', item['ks_model_id_2']), ('name', '=', item['ks_date_filter_field_2'])])
if ks_record_id:
item['ks_date_filter_field_2'] = ks_record_id.id
else:
item['ks_date_filter_field_2'] = False
item['ks_model_id_2'] = ks_model_id_2
else:
item['ks_date_filter_field_2'] = False
item['ks_record_field_2'] = False
item['ks_model_id'] = ks_model_id
# if item['ks_currency_id']:
# ks_currency = self.env['res.currency'].browse(item['ks_currency_id']).name
# item['ks_currency_id'] = self.env['res.currency'].search([('name','=',ks_currency)])
item['ks_goal_liness'] = False
item['ks_item_start_date'] = item['ks_item_start_date'] if \
item['ks_item_start_date'] else False
item['ks_item_end_date'] = item['ks_item_end_date'] if \
item['ks_item_end_date'] else False
item['ks_item_start_date_2'] = item['ks_item_start_date_2'] if \
item['ks_item_start_date_2'] else False
item['ks_item_end_date_2'] = item['ks_item_end_date_2'] if \
item['ks_item_end_date_2'] else False
return item
except Exception as e:
raise ValidationError('JSON file not supported.')
@api.model
def update_child_board(self, action, dashboard_id, data):

View File

@ -112,15 +112,27 @@ def ks_time_addition(self, gb, query):
tz_convert = field_type == 'datetime' and self._context.get('tz') in pytz.all_timezones
qualified_field = self._inherits_join_calc(self._table, split[0], query)
if temporal:
display_formats = {
'minute': 'hh:mm dd MMM',
'hour': 'hh:00 dd MMM',
'day': 'dd MMM yyyy', # yyyy = normal year
'week': "'W'w YYYY", # w YYYY = ISO week-year
'month': 'MMMM yyyy',
'quarter': 'QQQ yyyy',
'year': 'yyyy',
}
lang = self.env['res.lang']._lang_get(self.env.user.lang).time_format
if '%H' in lang:
display_formats = {
'minute': 'HH:mm dd MMM',
'hour': 'HH:00 dd MMM',
'day': 'dd MMM yyyy', # yyyy = normal year
'week': "'W'w YYYY", # w YYYY = ISO week-year
'month': 'MMMM yyyy',
'quarter': 'QQQ yyyy',
'year': 'yyyy',
}
else:
display_formats = {
'minute': 'hh:mm dd MMM',
'hour': 'hh:00 dd MMM',
'day': 'dd MMM yyyy', # yyyy = normal year
'week': "'W'w YYYY", # w YYYY = ISO week-year
'month': 'MMMM yyyy',
'quarter': 'QQQ yyyy',
'year': 'yyyy',
}
time_intervals = {
'minute': dateutil.relativedelta.relativedelta(minutes=1),
'hour': dateutil.relativedelta.relativedelta(hours=1),
@ -153,19 +165,24 @@ class KsDashboardNinjaItems(models.Model):
_name = 'ks_dashboard_ninja.item'
_description = 'Dashboard Ninja items'
name = fields.Char(string="Name", size=256, help="The item will be represented by this unique name.")
name = fields.Char(string="Name", size=256,
translate=True,
help="The item will be represented by this unique name.")
ks_info = fields.Text(string="Item Description",
translate=True)
ks_model_id = fields.Many2one('ir.model', string='Model',
domain="[('access_ids','!=',False),('transient','=',False),"
"('model','not ilike','base_import%'),('model','not ilike','ir.%'),"
"('model','not ilike','base_import%'),'|',('model','not ilike','ir.%'),('model','=ilike','_%ir.%'),"
"('model','not ilike','web_editor.%'),('model','not ilike','web_tour.%'),"
"('model','!=','mail.thread'),('model','not ilike','ks_dash%'),('model','not ilike','ks_to%')]",
"('model','!=','mail.thread'),('model','not ilike','ks_dash%'), ('model','not ilike','ks_to%')]",
help="Data source to fetch and read the data for the creation of dashboard items. ")
ks_dashboard_board_template_id = fields.Many2one('ks_dashboard_ninja.board_template', string="Dashboard Template")
ks_domain = fields.Char(string="Domain", help="Define conditions for filter. ")
ks_model_id_2 = fields.Many2one('ir.model', string='Kpi Model',
domain="[('access_ids','!=',False),('transient','=',False),"
"('model','not ilike','base_import%'),('model','not ilike','ir.%'),"
"('model','not ilike','base_import%'),'|',('model','not ilike','ir.%'),('model','=ilike','_%ir.%'),"
"('model','not ilike','web_editor.%'),('model','not ilike','web_tour.%'),"
"('model','!=','mail.thread'),('model','not ilike','ks_dash%'), ('model','not ilike','ks_to%')]")
@ -231,6 +248,10 @@ class KsDashboardNinjaItems(models.Model):
('t_month', 'This Month'),
('t_quarter', 'This Quarter'),
('t_year', 'This Year'),
('td_week', 'Week to Date'),
('td_month', 'Month to Date'),
('td_quarter', 'Quarter to Date'),
('td_year', 'Year to Date'),
('n_day', 'Next Day'),
('n_week', 'Next Week'),
('n_month', 'Next Month'),
@ -246,6 +267,7 @@ class KsDashboardNinjaItems(models.Model):
('l_quarter', 'Last 90 days'),
('l_year', 'Last 365 days'),
('ls_past_until_now', 'Past Till Now'),
('ls_pastuntil_lastmonth', 'Past Till: 30 days ago'),
('ls_pastwithout_now', ' Past Excluding Today'),
('n_future_starting_now', 'Future Starting Now'),
('n_futurestarting_tomorrow', 'Future Starting Tomorrow'),
@ -279,6 +301,10 @@ class KsDashboardNinjaItems(models.Model):
('t_month', 'This Month'),
('t_quarter', 'This Quarter'),
('t_year', 'This Year'),
('td_week', 'Week to Date'),
('td_month', 'Month to Date'),
('td_quarter', 'Quarter to Date'),
('td_year', 'Year to Date'),
('n_day', 'Next Day'),
('n_week', 'Next Week'),
('n_month', 'Next Month'),
@ -294,6 +320,7 @@ class KsDashboardNinjaItems(models.Model):
('l_quarter', 'Last 90 days'),
('l_year', 'Last 365 days'),
('ls_past_until_now', 'Past Till Now'),
('ls_pastuntil_lastmonth', 'Past Till: 30 days ago'),
('ls_pastwithout_now', ' Past Excluding Today'),
('n_future_starting_now', 'Future Starting Now'),
('n_futurestarting_tomorrow', 'Future Starting Tomorrow'),
@ -366,7 +393,7 @@ class KsDashboardNinjaItems(models.Model):
"('ttype','=','integer'),('ttype','=','float'),"
"('ttype','=','monetary')]",
string="Measure 1", help='Data points to be selected.')
ks_chart_is_cumulative = fields.Boolean('Is Cumulative')
ks_chart_cumulative_field = fields.Many2many('ir.model.fields', 'ks_dn_cumulative_measure_field_rel',
'measure_cumulative_field_id',
'cumulative_field_id',
@ -375,7 +402,7 @@ class KsDashboardNinjaItems(models.Model):
"('store','=',True),'|','|',"
"('ttype','=','integer'),('ttype','=','float'),"
"('ttype','=','monetary')]",
string="Cumulative Field", help='Data points to be selected.')
string="Cumulative Fields", help='Data points to be selected.')
ks_chart_cumulative = fields.Boolean("Cumulative As Line")
ks_chart_measure_field_2 = fields.Many2many('ir.model.fields', 'ks_dn_measure_field_rel_2', 'measure_field_id_2',
@ -414,6 +441,11 @@ class KsDashboardNinjaItems(models.Model):
ks_kpi_data = fields.Char(string="KPI Data", compute="ks_get_kpi_data", compute_sudo=False)
# ------------------------ Tile View Fields ------------------------------
ks_tile_data = fields.Char(string="TILE Data", compute="ks_get_tile_data", compute_sudo=False)
ks_chart_item_color = fields.Selection(
[('default', 'Default'), ('cool', 'Cool'), ('warm', 'Warm'), ('neon', 'Neon')],
string="Chart Color Palette", default="default", help='Select the display preference. ')
@ -481,20 +513,11 @@ class KsDashboardNinjaItems(models.Model):
help="Provides the visibility of multiplier field")
ks_multiplier = fields.Float(string="Multiplier", default=1, help="Provides the multiplication of record value")
# Adding refresh per item override global update interval
ks_update_items_data = fields.Selection([
('15000', '15 Seconds'),
('30000', '30 Seconds'),
('45000', '45 Seconds'),
('60000', '1 minute'),
('120000', '2 minute'),
('300000', '5 minute'),
('600000', '10 minute'),
], string="Item Update Interval", default=lambda self: self._context.get('ks_set_interval', False),
help=" Data will be refreshed after the selected interval.")
# User can select custom units for measure
ks_unit = fields.Boolean(string="Show Custom Unit", default=False, help='Display the unit of the data.')
ks_currency_id= fields.Many2one("res.currency",string="Currency", domain="['|', ('active', '=', False), ('active', '=', True)]")
ks_unit_selection = fields.Selection([
('monetary', 'Monetary'),
('custom', 'Custom'),
@ -529,14 +552,7 @@ class KsDashboardNinjaItems(models.Model):
help="To Change the number format showing in chart to given option")
ks_button_color = fields.Char(string="Top Button Color",
default="#000000,0.99")
ks_auto_update_type = fields.Selection(
[('ks_live_update', 'Update at every instance.'),
('ks_update_interval', ' Update after the selected interval')],
string='Auto Update Type',
default=lambda self: 'ks_update_interval' if self._context.get('ks_set_interval', False) else False,
help='Select the update type.')
ks_show_live_pop_up = fields.Boolean(string='Show Live Update Pop Up',
help='Checkbox to enable notification after every update. ')
ks_is_client_action = fields.Boolean('Client Action', default=False)
ks_client_action = fields.Many2one('ir.actions.client',
@ -552,6 +568,153 @@ class KsDashboardNinjaItems(models.Model):
ks_precision_digits = fields.Integer('Digits', compute="_ks_compute_precision_digits", store=True, readonly=False)
ks_data_label_type = fields.Selection([('percent', 'Percent'), ('value', 'Value')], string='Show Data Value Type',
help='When "Show Data Value Type" selected this field enables to select label type in percent or value',
default='percent')
ks_as_of_now = fields.Boolean("Data Till Now",
help="Display the total sum of each legends as it grows with times")
@api.model
def create_ai_dash(self, data, ks_dash_id, model):
try:
result= []
for item in data:
ks_measure_field_ids = []
value = {}
chart_switch = {
'bar': "ks_bar_chart",
'pie': 'ks_pie_chart',
'donut': 'ks_doughnut_chart',
'area': 'ks_area_chart',
'line': 'ks_line_chart',
'polar': 'ks_polarArea_chart',
'horizontalbar': 'ks_horizontalBar_chart',
'table': "ks_list_view"
}
if item["chart_type"].lower() in ['bar', 'line', 'pie', 'area', 'donut', 'polar', 'horizontalbar']:
ks_measure_id = self.env['ir.model.fields'].search(
[('name', '=', item["aggregations"][0]["field"]), ('model', '=', model)])
if ks_measure_id and ks_measure_id['ttype'] in ['integer','float','monetary']:
ks_measure_field_ids.append(ks_measure_id.id)
value["ks_chart_measure_field"] = [(6, 0, ks_measure_field_ids)]
ks_record_id = self.env['ir.model.fields'].search(
[('name', '=', item["group_by_column"]), ('model', '=', model)])
if ks_record_id:
value['ks_chart_relation_groupby'] = ks_record_id.id
if ks_record_id['ttype'] == "datetime" or ks_record_id['ttype'] == "date":
value['ks_chart_date_groupby'] = "month"
value["name"] = item["chart_name"]
ks_model_id = self.env['ir.model'].search([('model', '=', model)]).id
value['ks_model_id'] = ks_model_id
if item["aggregations"][0]["type"] == 'avg':
value['ks_chart_data_count_type'] = 'average'
else:
value['ks_chart_data_count_type'] = item["aggregations"][0]["type"]
value['ks_dashboard_item_type'] = chart_switch.get(item['chart_type'], False)
value['ks_dashboard_ninja_board_id'] = ks_dash_id
if ks_measure_field_ids and ks_record_id and ks_model_id:
try:
ks_result = self.create(value)
result.append(ks_result)
except Exception as e:
result
elif item["chart_type"].lower() == "table":
value["name"] = item["chart_name"]
value['ks_dashboard_ninja_board_id'] = ks_dash_id
value['ks_dashboard_item_type'] = chart_switch.get(item['chart_type'], False)
ks_model_id = self.env['ir.model'].search([('model', '=', model)]).id
value['ks_model_id'] = ks_model_id
ks_measure_id = self.env['ir.model.fields'].search(
[('name', '=', item["aggregations"][0]["field"]), ('model', '=', model)])
if ks_measure_id and ks_measure_id['ttype'] in ['integer','float','monetary']:
ks_measure_field_ids.append(ks_measure_id.id)
value["ks_list_view_group_fields"] = [(6, 0, ks_measure_field_ids)]
# value["ks_list_view_fields"] = [(6, 0, ks_measure_field_ids)]
ks_record_id = self.env['ir.model.fields'].search(
[('name', '=', item["group_by_column"]), ('model', '=', model)])
if ks_record_id:
value['ks_chart_relation_groupby'] = ks_record_id.id
if ks_record_id['ttype'] == "datetime" or ks_record_id['ttype'] == "date":
value['ks_chart_date_groupby'] = "month"
value['ks_list_view_type'] = 'grouped'
if ks_measure_field_ids and ks_record_id and ks_model_id:
try:
ks_result = self.create(value)
result.append(ks_result)
except Exception as e:
result
elif item["chart_type"].lower() == "kpi":
value["name"] = item["chart_name"]
value['ks_dashboard_ninja_board_id'] = ks_dash_id
value['ks_dashboard_item_type'] = "ks_kpi"
ks_model_id = self.env['ir.model'].search([('model', '=', model)]).id
value['ks_model_id'] = ks_model_id
ks_measure_id = self.env['ir.model.fields'].search(
[('name', '=', item["aggregations"][0]["field"]), ('model', '=', model)])
if ks_measure_id:
value["ks_record_field"] = ks_measure_id.id
if item["aggregations"][0]["type"] == 'avg':
value['ks_record_count_type'] = 'average'
else:
value['ks_record_count_type'] = item["aggregations"][0]["type"]
value['ks_background_color'] = "#ffffff,0.99"
value['ks_default_icon_color'] = "#000000,0.99"
value['ks_font_color'] = "#000000,0.99"
value['ks_button_color'] = "#000000,0.99"
if ks_measure_id and ks_model_id:
try:
ks_result = self.create(value)
result.append(ks_result)
except Exception as e:
result
else:
pass
if len(result):
return "success"
else:
return "Abort"
except:
raise ValidationError(_("Getting invalid response from AI, please try again"))
# Making model, csv and excel field invisible on condition.
@api.onchange('data_source')
def make_invisible(self):
if self.data_source == 'excel':
self.excel_bool = True
self.model_bool = False
self.csv_bool = False
elif self.data_source == 'odoo':
self.model_bool = True
self.excel_bool = False
self.csv_bool = False
elif self.data_source == 'csv':
self.csv_bool = True
self.model_bool = False
self.excel_bool = False
else:
self.model_bool = False
self.excel_bool = False
self.csv_bool = False
# Reading the Csv file
@api.onchange('ks_year_period', 'ks_year_period_2')
def ks_year_neg_val_not_allow(self):
for rec in self:
@ -589,7 +752,8 @@ class KsDashboardNinjaItems(models.Model):
# default = lambda self: self.sudo().env.ref('ks_dashboard_ninja.ks_dashboard_ninja_precision')
@api.onchange('ks_multiplier_active', 'ks_chart_measure_field', 'ks_list_view_group_fields')
@api.onchange('ks_multiplier_active', 'ks_chart_measure_field',
'ks_chart_measure_field_2' ,'ks_list_view_group_fields')
def _ks_compute_multiplier_lines(self):
for rec in self:
rec.ks_multiplier_lines = [(5, 0, 0)]
@ -598,16 +762,32 @@ class KsDashboardNinjaItems(models.Model):
if rec.ks_dashboard_item_type == 'ks_list_view' and rec.ks_list_view_type == 'grouped':
ks_chart_measure_fields = rec.ks_list_view_group_fields
ks_temp_list = []
ks_chart_measure_id = []
for ks_chart_measure_field in ks_chart_measure_fields:
ks_dict = {
'ks_dashboard_item_id': rec.id,
'ks_multiplier_fields': ks_chart_measure_field.ids[0],
'ks_multiplier_value': 1
}
ks_chart_measure_id.append(ks_chart_measure_field.ids[0])
ks_line = self.env['ks_dashboard_item.multiplier'].create(ks_dict)
ks_temp_list.append(ks_line.id)
if rec.ks_chart_measure_field_2:
for ks_chart_measure_field in rec.ks_chart_measure_field_2:
if ks_chart_measure_field.ids[0] not in ks_chart_measure_id:
ks_dict = {
'ks_dashboard_item_id': rec.id,
'ks_multiplier_fields': ks_chart_measure_field.ids[0],
'ks_multiplier_value': 1
}
ks_line = self.env['ks_dashboard_item.multiplier'].create(ks_dict)
ks_temp_list.append(ks_line.id)
# rec.ks_multiplier_lines = [(6, 0, [])]
# rec.ks_multiplier_lines = [(6, 0, ks_temp_list)]
rec.ks_multiplier_lines = [(6, 0, [])]
rec.ks_multiplier_lines = [(6, 0, ks_temp_list)]
if len(rec.ks_chart_measure_field) == 0:
rec.ks_chart_cumulative_field = False
# if rec.ks_chart_cumulative_field:
@ -714,6 +894,11 @@ class KsDashboardNinjaItems(models.Model):
if 'ks_goal_lines' not in default:
default['ks_goal_lines'] = [(0, 0, line.copy_data()[0]) for line in self.ks_goal_lines]
if 'ks_multiplier_lines' not in default:
default['ks_multiplier_lines'] = [(0, 0, line.copy_data()[0]) for line in self.ks_multiplier_lines]
return super(KsDashboardNinjaItems, self).copy_data(default)
def copy(self, default=None):
@ -1011,7 +1196,7 @@ class KsDashboardNinjaItems(models.Model):
if selected_start_date and selected_end_date:
if rec.ks_compare_period:
ks_compare_period = abs(rec.ks_compare_period)
if ks_compare_period > 100:
if ks_compare_period > 100 and rec.ks_date_filter_selection.split('_')[1] != 'day':
ks_compare_period = 100
if rec.ks_compare_period > 0:
selected_end_date = selected_end_date + (
@ -1076,14 +1261,13 @@ class KsDashboardNinjaItems(models.Model):
ks_extensiom_domain = ks_extensiom_domain.replace('"%UID"', str(self.env.user.id))
if "%UID" in ks_extensiom_domain:
ks_extensiom_domain = ks_extensiom_domain.replace("'%UID'", str(self.env.user.id))
print(ks_extensiom_domain)
if ks_extensiom_domain and "%MYCOMPANY" in ks_extensiom_domain:
ks_extensiom_domain = ks_extensiom_domain.replace('"%MYCOMPANY"', str(self.env.company.id))
if "%MYCOMPANY" in ks_extensiom_domain:
ks_extensiom_domain = ks_extensiom_domain.replace("'%MYCOMPANY'", str(self.env.company.id))
ks_domain = eval(ks_extensiom_domain)
ks_domain = safe_eval(ks_extensiom_domain)
return ks_domain
@api.onchange('ks_domain_extension')
@ -1176,8 +1360,9 @@ class KsDashboardNinjaItems(models.Model):
rec.ks_goal_lines = False
rec.ks_goal_enable = False
rec.ks_fill_temporal = False
rec.ks_as_of_now = False
@api.onchange('ks_chart_relation_sub_groupby', 'ks_fill_temporal', 'ks_goal_lines')
@api.onchange('ks_chart_relation_sub_groupby', 'ks_fill_temporal','ks_as_of_now', 'ks_goal_lines')
def ks_empty_limit(self):
for rec in self:
if rec.ks_chart_relation_sub_groupby or rec.ks_fill_temporal or rec.ks_goal_lines:
@ -1185,6 +1370,7 @@ class KsDashboardNinjaItems(models.Model):
if rec.ks_chart_relation_sub_groupby:
rec.ks_chart_cumulative_field = False
rec.ks_fill_temporal = False
rec.ks_as_of_now = False
# @api.onchange('ks_chart_cumulative_field')
# def ks_onchange_cumutive(self):
@ -1292,11 +1478,11 @@ class KsDashboardNinjaItems(models.Model):
ks_chart_data['datasets'].append(
{'data': [], 'label': rec.ks_chart_cumulative_field[res].field_description,
'ks_chart_cumulative_field': True})
'ks_chart_cumulative_field': True, 'ks_as_of_now': rec.ks_as_of_now})
else:
ks_chart_data['datasets'].append(
{'data': [], 'label': rec.ks_chart_measure_field[res].field_description,
'ks_chart_cumulative_field': False})
'ks_chart_cumulative_field': False, 'ks_as_of_now': rec.ks_as_of_now})
# ks_chart_measure_field = [res.name for res in rec.ks_chart_measure_field]
ks_chart_groupby_relation_field = rec.ks_chart_relation_groupby.name
@ -1309,6 +1495,8 @@ class KsDashboardNinjaItems(models.Model):
if rec.ks_sort_by_order:
orderby = orderby + " " + rec.ks_sort_by_order
limit = rec.ks_record_data_limit if rec.ks_record_data_limit and rec.ks_record_data_limit > 0 else 5000
if rec.ks_as_of_now:
limit=5000
if ((rec.ks_chart_data_count_type != "count" and ks_chart_measure_field) or (
rec.ks_chart_data_count_type == "count" and not ks_chart_measure_field)) \
@ -1467,6 +1655,7 @@ class KsDashboardNinjaItems(models.Model):
ks_goal_datasets = {
'label': 'Target',
'data': goal_dataset,
'ks_as_of_now': False
}
if rec.ks_goal_bar_line:
ks_goal_datasets['type'] = 'line'
@ -1659,12 +1848,13 @@ class KsDashboardNinjaItems(models.Model):
xlabels = []
series = []
values = {}
values = {'ks_sub_domain': {}}
domains = {}
for data in chart_data:
label = data['labels']
serie = data['series']
domain = data['domain']
ks_sub_group_domain = data['domain'].copy()
if (len(xlabels) == 0) or (label not in xlabels):
xlabels.append(label)
@ -1679,12 +1869,16 @@ class KsDashboardNinjaItems(models.Model):
value = data['value']
counter = 0
for seri in serie:
if seri not in values:
values[seri] = {}
values[seri] = {'ks_sub_domain': {}}
if label in values[seri]:
values[seri][label] = values[seri][label] + value[counter]
values[seri]['ks_sub_domain'][label] = ks_sub_group_domain
else:
values[seri][label] = value[counter]
values[seri]['ks_sub_domain'][label] = ks_sub_group_domain
counter += 1
final_datasets = []
@ -1696,13 +1890,15 @@ class KsDashboardNinjaItems(models.Model):
for dataset in final_datasets:
ks_dataset = {
'value': [],
'key': dataset
'key': dataset,
}
for label in xlabels:
ks_dataset['value'].append({
'domain': domains[label],
'x': label,
'y': values[dataset][label] if label in values[dataset] else 0
'y': values[dataset][label] if label in values[dataset] else 0,
'ks_sub_domain': values[dataset]['ks_sub_domain'][label] if values[dataset].get('ks_sub_domain', False) and values[dataset]['ks_sub_domain'].get(label, False) else []
})
ks_data.append(ks_dataset)
@ -1735,22 +1931,25 @@ class KsDashboardNinjaItems(models.Model):
ks_chart_data['domains'].append(res['domain'])
if rec.ks_chart_measure_field_2 and rec.ks_dashboard_item_type == 'ks_bar_chart':
ks_chart_data['ks_show_second_y_scale'] = True
values_2 = {}
values_2 = {'ks_sub_domain': {}}
series_2 = []
for data in chart_sub_data:
label = data['labels']
serie = data['series']
series_2 = series_2 + serie
value = data['value']
ks_sub_domain = data['domain'].copy()
counter = 0
for seri in serie:
if seri not in values_2:
values_2[seri] = {}
values_2[seri] = {'ks_sub_domain': {}}
if label in values_2[seri]:
values_2[seri][label] = values_2[seri][label] + value[counter]
values_2[seri]['ks_sub_domain'][label] = ks_sub_domain
else:
values_2[seri][label] = value[counter]
values_2[seri]['ks_sub_domain'][label] = ks_sub_domain
counter += 1
final_datasets_2 = []
for serie in series_2:
@ -1760,12 +1959,13 @@ class KsDashboardNinjaItems(models.Model):
for dataset in final_datasets_2:
ks_dataset = {
'value': [],
'key': dataset
'key': dataset,
}
for label in xlabels:
ks_dataset['value'].append({
'x': label,
'y': values_2[dataset][label] if label in values_2[dataset] else 0
'y': values_2[dataset][label] if label in values_2[dataset] else 0,
'ks_sub_domain': values_2[dataset]['ks_sub_domain'][label] if values_2[dataset].get('ks_sub_domain', False) and values_2[dataset]['ks_sub_domain'].get(label, False) else []
})
ks_data_2.append(ks_dataset)
@ -1774,20 +1974,25 @@ class KsDashboardNinjaItems(models.Model):
'label': ks_dat['key'],
'data': [],
'type': 'line',
'yAxisID': 'y-axis-1'
'yAxisID': 'y-axis-1',
'ks_sub_domain': []
}
for res in ks_dat['value']:
dataset['data'].append(res['y'])
dataset['ks_sub_domain'].append(
res['ks_sub_domain'] if res.get('ks_sub_domain', False) else [])
ks_chart_data['datasets'].append(dataset)
for ks_dat in ks_data:
dataset = {
'label': ks_dat['key'],
'data': []
'data': [],
'ks_sub_domain': []
}
for res in ks_dat['value']:
dataset['data'].append(res['y'])
dataset['ks_sub_domain'].append(res['ks_sub_domain'] if res.get('ks_sub_domain',False) else [])
ks_chart_data['datasets'].append(dataset)
@ -1800,6 +2005,7 @@ class KsDashboardNinjaItems(models.Model):
ks_goal_datasets = {
'label': 'Target',
'data': goal_dataset,
'ks_as_of_now': False
}
if rec.ks_goal_bar_line and rec.ks_dashboard_item_type != 'ks_horizontalBar_chart':
ks_goal_datasets['type'] = 'line'
@ -1811,11 +2017,15 @@ class KsDashboardNinjaItems(models.Model):
if self.ks_multiplier_active:
for ks_multiplier in self.ks_multiplier_lines:
for i in range(0, len(ks_chart_data['datasets'])):
if ks_multiplier.ks_multiplier_fields.field_description in ks_chart_data['datasets'][i][
'label']:
data_values = ks_chart_data['datasets'][i]['data']
data_values = list(map(lambda x: ks_multiplier.ks_multiplier_value * x, data_values))
ks_chart_data['datasets'][i]['data'] = data_values
try:
if ks_multiplier.ks_multiplier_fields.field_description != False and ks_multiplier.ks_multiplier_fields.field_description in \
ks_chart_data['datasets'][i][
'label']:
data_values = ks_chart_data['datasets'][i]['data']
data_values = list(map(lambda x: ks_multiplier.ks_multiplier_value * x, data_values))
ks_chart_data['datasets'][i]['data'] = data_values
except Exception as e:
raise ValidationError('JSON file not supported.')
# res_list = [i for n, i in enumerate(ks_chart_data.get('datasets',[])) if i not in ks_chart_data.get('datasets',[])[n + 1:]]
# ks_chart_data['datasets'] = res_list
return json.dumps(ks_chart_data)
@ -2335,6 +2545,7 @@ class KsDashboardNinjaItems(models.Model):
rec.ks_chart_cumulative = False
rec.ks_multiplier_active = False
rec.ks_model_id_2 = False
rec.ks_chart_measure_field_2 = False
if rec.ks_dashboard_item_type == 'ks_to_do':
rec.ks_model_id_2 = False
rec.ks_model_id = False
@ -2351,9 +2562,32 @@ class KsDashboardNinjaItems(models.Model):
rec.ks_item_start_date = ks_date_data["selected_start_date"]
rec.ks_item_end_date = ks_date_data["selected_end_date"]
@api.depends('ks_dashboard_item_type', 'ks_record_count','ks_model_id','ks_multiplier','ks_multiplier_active','ks_unit', 'ks_unit_selection', 'ks_currency_id')
def ks_get_tile_data(self):
for rec in self:
rec.ks_tile_data = rec._ksGettileData(domain=[])
def _ksGettileData(self, domain=[]):
rec = self
if rec.ks_dashboard_item_type and rec.ks_dashboard_item_type == 'ks_tile' and rec.ks_model_id :
ks_tile_data = {'ks_currency': 0, 'ks_field': "", 'ks_selection': ""}
if rec.ks_unit and rec.ks_unit_selection == 'monetary':
ks_tile_data['ks_selection'] += rec.ks_unit_selection
ks_tile_data['ks_currency'] += rec.env.user.company_id.currency_id.id
elif rec.ks_unit and rec.ks_unit_selection == 'custom':
ks_tile_data['ks_selection'] += rec.ks_unit_selection
if rec.ks_currency_id:
ks_tile_data['ks_field'] += rec.ks_currency_id.symbol
return json.dumps(ks_tile_data)
else:
return False
@api.depends('ks_dashboard_item_type', 'ks_goal_enable', 'ks_standard_goal_value', 'ks_record_count',
'ks_record_count_2', 'ks_previous_period', 'ks_compare_period', 'ks_year_period',
'ks_compare_period_2', 'ks_year_period_2', 'ks_domain_extension_2')
'ks_compare_period_2', 'ks_year_period_2', 'ks_domain_extension_2','ks_unit','ks_unit_selection','ks_currency_id')
def ks_get_kpi_data(self):
for rec in self:
rec.ks_kpi_data = rec._ksGetKpiData(domain1=[], domain2=[])
@ -2364,11 +2598,22 @@ class KsDashboardNinjaItems(models.Model):
ks_kpi_data = []
ks_record_count = 0.0
ks_kpi_data_model_1 = {}
ks_kpi_data_model_1 = {'ks_currency': 0, 'ks_field': "", 'ks_selection': ""}
ks_record_count = rec._ksGetRecordCount(domain1)
ks_kpi_data_model_1['model'] = rec.ks_model_name
ks_kpi_data_model_1['record_field'] = rec.ks_record_field.field_description
ks_kpi_data_model_1['record_data'] = ks_record_count
if rec.ks_unit and rec.ks_unit_selection == 'monetary':
ks_kpi_data_model_1['ks_selection'] += rec.ks_unit_selection
ks_kpi_data_model_1['ks_currency'] += rec.env.user.company_id.currency_id.id
elif rec.ks_unit and rec.ks_unit_selection == 'custom':
ks_kpi_data_model_1['ks_selection'] += rec.ks_unit_selection
if rec.ks_currency_id:
ks_kpi_data_model_1['ks_field'] += rec.ks_currency_id.symbol
if rec.ks_goal_enable:
ks_kpi_data_model_1['target'] = rec.ks_standard_goal_value
ks_kpi_data.append(ks_kpi_data_model_1)
@ -2609,17 +2854,17 @@ class KsDashboardNinjaItems(models.Model):
if selected_start_date and selected_end_date:
if rec.ks_compare_period_2:
ks_compare_period_2 = abs(rec.ks_compare_period_2)
if ks_compare_period_2 > 100:
if ks_compare_period_2 > 100 and rec.ks_date_filter_selection_2.split('_')[1] != 'day':
ks_compare_period_2 = 100
if rec.ks_compare_period_2 > 0:
selected_end_date = selected_end_date + (
selected_end_date - selected_start_date) * ks_compare_period_2
if rec.ks_date_filter_field.ttype == "date" and rec.ks_date_filter_selection == 'l_day':
if rec.ks_date_filter_field_2.ttype == "date" and rec.ks_date_filter_selection_2 == 'l_day':
selected_end_date = selected_end_date + timedelta(days=ks_compare_period_2)
elif rec.ks_compare_period_2 < 0:
selected_start_date = selected_start_date - (
selected_end_date - selected_start_date) * ks_compare_period_2
if rec.ks_date_filter_field.ttype == "date" and rec.ks_date_filter_selection == 'l_day':
if rec.ks_date_filter_field_2.ttype == "date" and rec.ks_date_filter_selection_2 == 'l_day':
selected_start_date = selected_end_date - timedelta(days=ks_compare_period_2)
if rec.ks_year_period_2 and rec.ks_year_period_2 != 0:
@ -2763,6 +3008,8 @@ class KsDashboardNinjaItems(models.Model):
continue
except ZeroDivisionError:
data = 0
# if data > 0:
ks_chart_data['datasets'][counter]['ks_sub_domain'] = [(field_rec, '!=', False)]
ks_chart_data['datasets'][counter]['data'].append(data)
counter += 1
index += 1
@ -2792,6 +3039,9 @@ class KsDashboardNinjaItems(models.Model):
continue
except ZeroDivisionError:
data = 0
# if data > 0:
ks_chart_data['datasets'][counter]['ks_sub_domain'] = [(field_rec, '!=', False)]
ks_chart_data['datasets'][counter]['data'].append(data)
counter += 1
index += 1

View File

@ -1,15 +1,18 @@
import base64
import logging
from odoo import api, fields, models,_
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class KsDashboardNInjaImport(models.TransientModel):
_name = 'ks_dashboard_ninja.import'
_description = 'Import Dashboard'
ks_import_dashboard = fields.Binary(string="Upload Dashboard", attachment=True)
ks_top_menu_id = fields.Many2one('ir.ui.menu', string="Show Under Menu", required=True,
ks_top_menu_id = fields.Many2one('ir.ui.menu', domain="[('parent_id','=',False)]", string="Show Under Menu",
required=True,
default=lambda self: self.env['ir.ui.menu'].search(
[('name', '=', 'My Dashboard')]))

View File

@ -0,0 +1,31 @@
import base64
import logging
import requests
import json
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class KsAIDashboardFetch(models.TransientModel):
_name = 'ks_dashboard_ninja.fetch_key'
_description = 'Fetch API key'
ks_email_id = fields.Char(String="Email ID")
ks_api_key =fields.Char(string="Generated AI API Key")
ks_show_api_key = fields.Boolean(string="Show key",default=False)
def ks_fetch_details(self):
url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url')
if url and self.ks_email_id:
url = url + "/api/v1/ks_dn_fetch_api"
json_data = {'email':self.ks_email_id}
ks_ai_response = requests.post(url,data=json_data)
if ks_ai_response.status_code == 200:
ks_ai_response = json.loads(ks_ai_response.text)
self.ks_api_key = ks_ai_response
self.ks_show_api_key = True
else:
raise ValidationError(_("Error generates with following status %s"),ks_ai_response.status_code)

View File

@ -1,21 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import operator
import re
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError
from odoo.http import request
from odoo.modules import get_module_resource
class KSIrUiMenu(models.Model):
_inherit = "ir.ui.menu"
class ResCompany(models.Model):
_inherit = 'res.company'
def ksMenu(self, ks_xml_id):
print("@@@@@@@@@@@@@@@@@@@@@@@@@")
return self.env['ir.ui.menu'].search([('id','=',72)]).child_id[12].action.id
def ks_check_is_enterprise(self):
ks_temp = False
if len(self.env['ir.module.module'].sudo().search([('name', '=', 'web_enterprise')]))>0 \
and self.env['ir.module.module'].sudo().search([('name', '=', 'web_enterprise')],limit=1).state == \
'installed':
ks_temp = True
return ks_temp

View File

@ -0,0 +1,49 @@
from odoo import api, fields, models,_
from odoo.exceptions import ValidationError
import requests
import json
class ResConfig(models.TransientModel):
_inherit = "res.config.settings"
dn_api_key = fields.Char(string="Dashboard AI API Key",store=True,
config_parameter='ks_dashboard_ninja.dn_api_key')
url = fields.Char(string="URL", store=True,
config_parameter="ks_dashboard_ninja.url")
ks_email_id = fields.Char(string="Email ID",store=True,config_parameter="ks_dashboard_ninja.ks_email_id")
def Open_wizard(self):
if self.url and self.ks_email_id:
try:
url = self.url + "/api/v1/ks_dn_fetch_api"
json_data = {'email':self.ks_email_id,
'url':self.env['ir.config_parameter'].sudo().get_param('web.base.url'),
'db_name':self.env.cr.dbname
}
ks_ai_response = requests.post(url,data=json_data)
except Exception as e:
raise ValidationError(_("Please enter correct URL"))
if ks_ai_response.status_code == 200:
try:
ks_ai_response = json.loads(ks_ai_response.text)
except Exception as e:
ks_ai_response = False
if ks_ai_response == "success":
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success'),
'message': 'API key sent on Email ID',
'sticky': False,
}
}
elif ks_ai_response == 'key already generated':
raise ValidationError(
_("key already generated.If you need assistance, feel free to contact at sales@ksolves.com"))
else:
raise ValidationError(_("Either you have entered wrong URL path or there is some problem in sending request. If you need assistance, feel free to contact at sales@ksolves.com"))
else:
raise ValidationError(_("Some problem in sending request.Please contact at sales@ksolves.com"))
else:
raise ValidationError(_("Please enter URL and Email ID"))

View File

@ -22,4 +22,7 @@ access_ks_dashboard_ninja_ks_grid_per_company,ks_dashboard_ninja.ks_grid_per_com
access_ks_dashboard_wizard,access_ks_dashboard_wizard,model_ks_dashboard_wizard,,1,1,1,1
access_ir_model_ks_duplicate_dashboard_wizard,ks_duplicate_dashboard__wizard,model_ks_dashboard_duplicate_wizard,,1,1,1,1
access_ir_model_ks_delete_dashboard_wizard,ks_delete_dashboard__wizard,model_ks_dashboard_delete_wizard,,1,1,1,1
access_ir_model_ks_delete_dashboard_wizard,ks_delete_dashboard__wizard,model_ks_dashboard_delete_wizard,,1,1,1,1
access_ks_dashboard_ninja_arti_int,ks_dashboard_ninja.arti_int,model_ks_dashboard_ninja_arti_int,,1,1,1,1
access_ks_dashboard_ninja_ai_dashboard,ks_dashboard_ninja.ai_dashboard,model_ks_dashboard_ninja_ai_dashboard,,1,1,1,1
access_ks_dashboard_ninja_fetch_key,ks_dashboard_ninja.fetch_key,model_ks_dashboard_ninja_fetch_key,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
22 access_ir_model_ks_delete_dashboard_wizard ks_delete_dashboard__wizard model_ks_dashboard_delete_wizard 1 1 1 1
23 access_ks_dashboard_ninja_arti_int ks_dashboard_ninja.arti_int model_ks_dashboard_ninja_arti_int 1 1 1 1
24 access_ks_dashboard_ninja_ai_dashboard ks_dashboard_ninja.ai_dashboard model_ks_dashboard_ninja_ai_dashboard 1 1 1 1
25 access_ks_dashboard_ninja_fetch_key ks_dashboard_ninja.fetch_key model_ks_dashboard_ninja_fetch_key 1 1 1 1
26
27
28

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 MiB

After

Width:  |  Height:  |  Size: 5.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Some files were not shown because too many files have changed in this diff Show More