Merge branch 'dev_odex25_base' into dev_base_temp
|
|
@ -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>`__
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
## Module <cron_failure_notification>
|
||||
|
||||
#### 17.10.2023
|
||||
#### Version 14.0.1.0.0
|
||||
#### ADD
|
||||
|
||||
- Initial commit for Cron Failure Notification
|
||||
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
|
|
@ -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'))
|
||||
|
|
@ -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>
|
||||
|
|
@ -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', '>=', start_time), ('create_date', '<=', 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>
|
||||
|
|
@ -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
|
||||
|
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 310 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 576 B |
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 911 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 673 B |
|
After Width: | Height: | Size: 878 B |
|
After Width: | Height: | Size: 653 B |
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 839 B |
|
After Width: | Height: | Size: 427 B |
|
After Width: | Height: | Size: 627 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 988 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 589 B |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 967 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 757 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -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 & 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 -->
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Dashboard Ninja',
|
||||
'name': 'Dashboard Ninja With AI',
|
||||
|
||||
'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.
|
||||
|
|
@ -69,27 +69,40 @@ Dashboard Ninja v14.0,
|
|||
|
||||
'currency': 'EUR',
|
||||
|
||||
'price': '272.25',
|
||||
|
||||
'price': '287.19',
|
||||
|
||||
'website': 'https://store.ksolves.com/',
|
||||
|
||||
'maintainer': 'Ksolves India Ltd.',
|
||||
|
||||
'live_test_url': 'https://dn15demo.kappso.com/web/demo_login',
|
||||
'live_test_url': 'https://dn15demo.kappso.com/web#cids=1&menu_id=599&ks_dashboard_id=1&action=835',
|
||||
|
||||
'category': 'Tools',
|
||||
|
||||
'version': '14.0.1.7.2',
|
||||
'version': '14.0.2.0.0',
|
||||
|
||||
'support': 'sales@ksolves.com',
|
||||
|
||||
'images': ['static/description/banner.gif'],
|
||||
'images': ['static/description/Independence_sale.jpg'],
|
||||
|
||||
'depends': ['base', 'web', 'base_setup', 'bus'],
|
||||
|
||||
'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'],
|
||||
'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'],
|
||||
|
||||
'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'],
|
||||
'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'],
|
||||
|
||||
'demo': ['demo/ks_dashboard_ninja_demo.xml'],
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from . import ks_chart_export
|
||||
from . import ks_list_export
|
||||
|
||||
from . import ks_dashboard_export
|
||||
from . import ks_login
|
||||
|
|
@ -27,8 +27,8 @@ class KsListExport(ExportFormat, http.Controller):
|
|||
'context', 'params')(
|
||||
params)
|
||||
list_data = json.loads(list_data)
|
||||
if ks_export_boolean:
|
||||
item = request.env['ks_dashboard_ninja.item'].browse(int(item_id))
|
||||
if ks_export_boolean:
|
||||
ks_timezone = item._context.get('tz') or item.env.user.tz
|
||||
if not ks_timezone:
|
||||
ks_tzone = os.environ.get('TZ')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
@ -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"))
|
||||
|
|
@ -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. ")
|
||||
|
|
|
|||
|
|
@ -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,13 +612,17 @@ 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:
|
||||
if rec.grid_corners:
|
||||
keys_data = {rec.id: json.loads(rec.grid_corners.replace("\'", "\""))}
|
||||
keys_list = keys_data.keys()
|
||||
grid_corners = {}
|
||||
|
|
@ -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,6 +1032,7 @@ class KsDashboardNinjaBoard(models.Model):
|
|||
return ks_item
|
||||
|
||||
def ks_prepare_item(self, item):
|
||||
try:
|
||||
ks_measure_field_ids = []
|
||||
ks_measure_field_2_ids = []
|
||||
|
||||
|
|
@ -1113,6 +1164,9 @@ class KsDashboardNinjaBoard(models.Model):
|
|||
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 \
|
||||
|
|
@ -1125,6 +1179,8 @@ class KsDashboardNinjaBoard(models.Model):
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -112,6 +112,18 @@ 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:
|
||||
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',
|
||||
|
|
@ -153,10 +165,15 @@ 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%')]",
|
||||
help="Data source to fetch and read the data for the creation of dashboard items. ")
|
||||
|
|
@ -165,7 +182,7 @@ class KsDashboardNinjaItems(models.Model):
|
|||
|
||||
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,7 +762,20 @@ 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],
|
||||
|
|
@ -606,8 +783,11 @@ class KsDashboardNinjaItems(models.Model):
|
|||
}
|
||||
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][
|
||||
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
|
||||
|
|
|
|||
|
|
@ -2,14 +2,17 @@ import base64
|
|||
import logging
|
||||
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')]))
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
@ -23,3 +23,6 @@ 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_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
|
||||
|
|
After Width: | Height: | Size: 46 MiB |
|
After Width: | Height: | Size: 36 MiB |
|
After Width: | Height: | Size: 424 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 28 MiB |
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 27 MiB |
|
After Width: | Height: | Size: 30 MiB |
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 7.4 MiB After Width: | Height: | Size: 5.6 MiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |