This commit is contained in:
李鹏宇 2026-06-04 14:24:28 +08:00
parent 91dceac262
commit cd653ce24c
774 changed files with 125051 additions and 0 deletions

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from . import models
from . import controllers
from . import common_lib
from . import wizard
from odoo.api import Environment, SUPERUSER_ID
def uninstall_hook(env):
# env = Environment(cr, SUPERUSER_ID, {})
for rec in env['ks_dashboard_ninja.board'].search([]):
rec.ks_dashboard_client_action_id.unlink()
rec.ks_dashboard_menu_id.unlink()

View File

@ -0,0 +1,197 @@
# -*- coding: utf-8 -*-
{
'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.
""",
'description': """
Dashboard Ninja v18.0,
Odoo Dashboard,
Dashboard,
Dashboards,
Odoo apps,
Dashboard app,
HR Dashboard,
Sales Dashboard,
inventory Dashboard,
Lead Dashboard,
Opportunity Dashboard,
CRM Dashboard,
POS,
POS Dashboard,
Connectors,
Web Dynamic,
Report Import/Export,
Date Filter,
HR,
Sales,
Theme,
Tile Dashboard,
Dashboard Widgets,
Dashboard Manager,
Debranding,
Customize Dashboard,
Graph Dashboard,
Charts Dashboard,
Invoice Dashboard,
Project management,
ksolves,
ksolves apps,
Ksolves India Ltd.
Ksolves India Limited,
odoo dashboard apps
odoo dashboard app
odoo dashboard module
odoo modules
dashboards
powerful dashboards
beautiful odoo dashboard
odoo dynamic dashboard
all in one dashboard
multiple dashboard menu
odoo dashboard portal
beautiful odoo dashboard
odoo best dashboard
dashboard for management
Odoo custom dashboard
odoo dashboard management
odoo dashboard apps
create odoo dashboard
odoo dashboard extension
odoo dashboard module
""",
'author': 'Ksolves India Ltd.',
'license': 'OPL-1',
'currency': 'EUR',
'price': '518.62',
'website': 'https://store.ksolves.com/',
'maintainer': 'Ksolves India Ltd.',
'live_test_url': 'https://ksdndemo18.kappso.com/web/demo_login',
'category': 'Services',
'version': '18.0.1.0.1',
'support': 'sales@ksolves.com',
'images': ['static/description/DN 5.gif'],
'depends': ['base', 'web', 'base_setup', 'bus', 'base_geolocalize', 'mail'],
'data': [
'security/ir.model.access.csv',
'security/ks_security_groups.xml',
'data/ks_default_data.xml',
'data/ks_mail_cron.xml',
'data/dn_data.xml',
'data/sequence.xml',
'views/res_settings.xml',
'views/ks_dashboard_ninja_view.xml',
'views/ks_dashboard_ninja_item_view.xml',
'views/ks_dashboard_group_by.xml',
'views/ks_dashboard_csv_group_by.xml',
'views/ks_dashboard_action.xml',
'views/ks_import_dashboard_view.xml',
'wizard/ks_create_dashboard_wiz_view.xml',
'wizard/ks_duplicate_dashboard_wiz_view.xml',
'views/ks_ai_dashboard.xml',
'views/ks_whole_ai_dashboard.xml',
'views/ks_key_fetch.xml',
'views/webExtend.xml'
],
'demo': ['demo/ks_dashboard_ninja_demo.xml'],
'assets': {
'web.assets_backend': [
'ks_dashboard_ninja/static/src/css/ks_dashboard_ninja.scss',
'/ks_dashboard_ninja/static/lib/css/gridstack.min.css',
'/ks_dashboard_ninja/static/lib/css/awesomplete.css',
'ks_dashboard_ninja/static/src/css/ks_dashboard_ninja_item.css',
'ks_dashboard_ninja/static/src/css/ks_icon_container_modal.css',
'ks_dashboard_ninja/static/src/css/ks_dashboard_item_theme.css',
'ks_dashboard_ninja/static/src/css/ks_input_bar.css',
'ks_dashboard_ninja/static/src/css/ks_ai_dash.css',
'ks_dashboard_ninja/static/src/css/ks_dn_filter.css',
'ks_dashboard_ninja/static/src/css/ks_toggle_icon.css',
'ks_dashboard_ninja/static/src/css/ks_flower_view.css',
'ks_dashboard_ninja/static/src/css/ks_map_view.css',
'ks_dashboard_ninja/static/src/css/ks_funnel_view.css',
'ks_dashboard_ninja/static/src/css/ks_dashboard_options.css',
'/ks_dashboard_ninja/static/lib/js/gridstack-h5.js',
'ks_dashboard_ninja/static/src/js/ks_dashboard_ninja_new.js',
'ks_dashboard_ninja/static/src/js/ks_global_functions.js',
'ks_dashboard_ninja/static/lib/js/index.js',
'/ks_dashboard_ninja/static/lib/js/pdfmake.min.js',
'ks_dashboard_ninja/static/lib/js/percent.js',
'ks_dashboard_ninja/static/lib/js/pdf.min.js',
'ks_dashboard_ninja/static/lib/js/print.min.js',
'ks_dashboard_ninja/static/lib/js/Dataviz.js',
'ks_dashboard_ninja/static/lib/js/Material.js',
'ks_dashboard_ninja/static/lib/js/Moonrise.js',
'ks_dashboard_ninja/static/lib/js/exporting.js',
'ks_dashboard_ninja/static/lib/js/percent.js',
'ks_dashboard_ninja/static/lib/js/Animated.js',
'ks_dashboard_ninja/static/lib/js/worldLow.js',
'ks_dashboard_ninja/static/lib/js/map.js',
'ks_dashboard_ninja/static/lib/js/awesomplete.js',
'ks_dashboard_ninja/static/src/js/ks_dashboard_ninja_new.js',
'ks_dashboard_ninja/static/src/js/ks_global_functions.js',
'ks_dashboard_ninja/static/lib/js/xy.js',
'ks_dashboard_ninja/static/lib/js/radar.js',
'ks_dashboard_ninja/static/src/css/style.css',
'ks_dashboard_ninja/static/src/js/ks_filter_props_new.js',
'ks_dashboard_ninja/static/src/js/domainfix.js',
'ks_dashboard_ninja/static/src/js/ks_custom_dialog.js',
'ks_dashboard_ninja/static/src/js/ks_dashboard_graph_ai.js',
'ks_dashboard_ninja/static/src/js/ks_dashboard_kpi_ai.js',
'ks_dashboard_ninja/static/src/js/ks_dashboard_tile_ai.js',
'ks_dashboard_ninja/static/src/js/ks_dashboard_todo_ai.js',
'ks_dashboard_ninja/static/src/css/ks_dashboard_ninja_pro.css',
'ks_dashboard_ninja/static/src/css/ks_to_do_item.css',
'ks_dashboard_ninja/static/src/xml/**/*',
'ks_dashboard_ninja/static/src/css/ks_radial_chart.css',
'ks_dashboard_ninja/static/src/js/ks_ai_dash_action.js',
'ks_dashboard_ninja/static/src/components/**/*',
'ks_dashboard_ninja/static/src/widgets/**/*',
'ks_dashboard_ninja/static/src/scss/variable.scss',
'ks_dashboard_ninja/static/src/scss/create_dashboard.scss',
'ks_dashboard_ninja/static/src/scss/common.scss',
'ks_dashboard_ninja/static/src/scss/header.scss',
'ks_dashboard_ninja/static/src/scss/overview.scss',
'ks_dashboard_ninja/static/src/scss/screen.scss',
'/ks_dashboard_ninja/static/src/scss/explainAi.scss',
'/ks_dashboard_ninja/static/src/scss/chartInsight.scss',
'/ks_dashboard_ninja/static/src/scss/recentSearches.scss',
'/ks_dashboard_ninja/static/src/scss/Generate-ai.scss',
'/ks_dashboard_ninja/static/src/scss/chartScreen.scss',
'/ks_dashboard_ninja/static/src/scss/generateAI.scss',
'/ks_dashboard_ninja/static/src/scss/form_view.scss',
'ks_dashboard_ninja/static/src/js/file_uploader_extend.js',
'ks_dashboard_ninja/static/src/js/formView&NotificationExtend.js',
'ks_dashboard_ninja/static/src/js/modalsExtend.js',
'ks_dashboard_ninja/static/src/js/loader_screen.js',
'ks_dashboard_ninja/static/src/js/dashboards_overview.js',
'ks_dashboard_ninja/static/src/js/chatWizard.js',
'ks_dashboard_ninja/static/src/js/dnNavBarExtend.js',
'ks_dashboard_ninja/static/src/js/chatWizardIntegration.js',
'ks_dashboard_ninja/static/src/js/custom_filter.js',
'ks_dashboard_ninja/static/src/js/ks_dropdown.js',
],
},
'external_dependencies': {
'python': ['pandas', 'xlrd', 'openpyxl', 'gTTS', 'SQLAlchemy']
},
'uninstall_hook': 'uninstall_hook',
}

View File

@ -0,0 +1 @@
from . import ks_date_filter_selections

View File

@ -0,0 +1,343 @@
# -*- coding: utf-8 -*-
from odoo.fields import datetime
from odoo import _
from odoo.exceptions import ValidationError
from datetime import timedelta
import pytz
import os
import ast
import os.path
from dateutil import rrule
from dateutil.relativedelta import relativedelta
from odoo.tools.safe_eval import safe_eval
def ks_get_date(ks_date_filter_selection, self, type):
try:
timezone = self._context.get('tz')
except Exception as e:
timezone = self.env.user.tz
if not timezone:
ks_tzone = os.environ.get('TZ')
if ks_tzone:
timezone = ks_tzone
elif os.path.exists('/etc/timezone'):
ks_tzone = open('/etc/timezone').read()
timezone = ks_tzone[0:-1]
try:
datetime.now(pytz.timezone(timezone))
except Exception as e:
raise ValidationError(_("Please set the local timezone."))
else:
raise ValidationError(_("Please set the local timezone."))
series = ks_date_filter_selection
if ks_date_filter_selection in ['t_fiscal_year', 'n_fiscal_year', 'ls_fiscal_year']:
function_name = globals()["ks_date_series_" + series.split("_")[0]]
return function_name(series.split("_")[1], timezone, type,self)
else:
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
def ks_date_series_l(ks_date_selection, timezone, type, self=None):
ks_date_data = {}
date_filter_options = {
'day': 0,
'week': 7,
'month': 30,
'quarter': 90,
'year': 365,
'past': False,
'future': False
}
end_time = datetime.strptime(datetime.now(pytz.timezone(timezone)).strftime("%Y-%m-%d 23:59:59"),
'%Y-%m-%d %H:%M:%S')
start_time = datetime.strptime((datetime.now(pytz.timezone(timezone)) - timedelta(
days=date_filter_options[ks_date_selection])).strftime("%Y-%m-%d 00:00:00"), '%Y-%m-%d %H:%M:%S')
if type == 'date':
ks_date_data["selected_end_date"] = datetime.strptime(end_time.strftime("%Y-%m-%d"), '%Y-%m-%d')
ks_date_data["selected_start_date"] = datetime.strptime(start_time.strftime("%Y-%m-%d"), '%Y-%m-%d')
else:
ks_date_data["selected_end_date"] = ks_convert_into_utc(end_time, timezone)
ks_date_data["selected_start_date"] = ks_convert_into_utc(start_time, timezone)
return ks_date_data
# Current Date Ranges : Week, Month, Quarter, year
def ks_date_series_t(ks_date_selection, timezone, type, self=None):
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):
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):
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 = {}
date = datetime.now(pytz.timezone(timezone))
if date_state == "previous":
date = date - timedelta(days=1)
elif date_state == "next":
date = date + timedelta(days=1)
start_date = datetime(date.year, date.month, date.day)
end_date = datetime(date.year, date.month, date.day) + timedelta(days=1, seconds=-1)
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_week(date_state, timezone, type,self):
ks_date_data = {}
# date = datetime.now(pytz.timezone(timezone))
# ks_week = 0
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))
if date_state == "previous":
start_date = datetime.today() - relativedelta(weeks=1, weekday=start_Date(-1))
elif date_state == "next":
start_date = datetime.today() - relativedelta(weeks=-1, weekday=start_Date(-1))
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 = start_date + timedelta(days=6, hours=23, minutes=59, seconds=59, milliseconds=59)
ks_date_data["selected_end_date"] = end_date
else:
ks_date_data["selected_start_date"] = ks_convert_into_utc(start_date, timezone)
end_date = start_date + timedelta(days=6, hours=23, minutes=59, seconds=59, milliseconds=59)
ks_date_data["selected_end_date"] = ks_convert_into_utc(end_date, timezone)
return ks_date_data
def ks_get_date_range_from_month(date_state, timezone, type,self):
ks_date_data = {}
date = datetime.now(pytz.timezone(timezone))
year = date.year
month = date.month
if date_state == "previous":
month -= 1
if month == 0:
month = 12
year -= 1
elif date_state == "next":
month += 1
if month == 13:
month = 1
year += 1
end_year = year
end_month = month
if month == 12:
end_year += 1
end_month = 1
else:
end_month += 1
start_date = datetime(year, month, 1)
end_date = datetime(end_year, end_month, 1) - timedelta(seconds=1)
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_quarter(date_state, timezone, type,self):
ks_date_data = {}
date = datetime.now(pytz.timezone(timezone))
year = date.year
quarter = int((date.month - 1) / 3) + 1
if date_state == "previous":
quarter -= 1
if quarter == 0:
quarter = 4
year -= 1
elif date_state == "next":
quarter += 1
if quarter == 5:
quarter = 1
year += 1
start_date = datetime(year, 3 * quarter - 2, 1)
month = 3 * quarter
remaining = int(month / 12)
end_date = datetime(year + remaining, month % 12 + 1, 1) - timedelta(seconds=1)
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_year(date_state, timezone, type,self):
ks_date_data = {}
date = datetime.now(pytz.timezone(timezone))
year = date.year
if date_state == "previous":
year -= 1
elif date_state == "next":
year += 1
start_date = datetime(year, 1, 1)
end_date = datetime(year + 1, 1, 1) - timedelta(seconds=1)
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_past(date_state, self_tz, type, self):
ks_date_data = {}
date = datetime.now(pytz.timezone(self_tz))
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_pastwithout(date_state, self_tz, type,self):
ks_date_data = {}
date = datetime.now(pytz.timezone(self_tz))
hour = date.hour + 1
date = date - timedelta(hours=hour)
date = datetime.strptime(date.strftime("%Y-%m-%d 23:59:59"), '%Y-%m-%d %H:%M:%S')
ks_date_data["selected_start_date"] = False
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)
return ks_date_data
def ks_get_date_range_from_future(date_state, self_tz, type,self):
ks_date_data = {}
date = datetime.now(pytz.timezone(self_tz))
ks_date_data["selected_end_date"] = False
if type == 'date':
ks_date_data["selected_start_date"] = date.strptime(date.strftime("%Y-%m-%d"), '%Y-%m-%d')
else:
ks_date_data["selected_start_date"] = ks_convert_into_utc(date,self_tz)
return ks_date_data
def ks_get_date_range_from_futurestarting(date_state, self_tz, type,self):
ks_date_data = {}
date = datetime.now(pytz.timezone(self_tz))
date = date + timedelta(days=1)
start_date = datetime.strptime(date.strftime("%Y-%m-%d 00:00:00"), '%Y-%m-%d %H:%M:%S')
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"] = False
else:
ks_date_data["selected_start_date"] = ks_convert_into_utc(start_date, self_tz)
ks_date_data["selected_end_date"] = False
return ks_date_data
def ks_convert_into_utc(datetime, timezone):
ks_tz = timezone and pytz.timezone(timezone) or pytz.UTC
return ks_tz.localize(datetime.replace(tzinfo=None), is_dst=False).astimezone(pytz.UTC).replace(tzinfo=None)
def ks_convert_into_local(datetime, timezone):
ks_tz = timezone and pytz.timezone(timezone) or pytz.UTC
return pytz.UTC.localize(datetime.replace(tzinfo=None), is_dst=False).astimezone(ks_tz).replace(tzinfo=None)

View File

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

View File

@ -0,0 +1,128 @@
import io
import json
import logging
import operator
from odoo.exceptions import ValidationError
from odoo.http import content_disposition, request
from odoo.tools import pycompat
from werkzeug.exceptions import InternalServerError
from odoo import http
from odoo.addons.web.controllers.export import ExportXlsxWriter
_logger = logging.getLogger(__name__)
class KsChartExport(http.Controller):
def base(self, data):
params = json.loads(data)
if not params.get('chart_data'):
raise ValidationError("Chart data not present")
header,chart_data = operator.itemgetter('header','chart_data')(params)
chart_data = json.loads(chart_data)
if isinstance(chart_data['labels'], list):
chart_data['labels'] = [str(label) for label in chart_data['labels']]
chart_data['labels'].insert(0,'Measure')
columns_headers = chart_data['labels']
import_data = []
excel_fields = []
for dataset in chart_data['datasets']:
dataset['data'].insert(0, dataset['label'])
import_data.append(dataset['data'])
for i in range(len(columns_headers)):
ks_type_obj = {}
if (len(import_data)):
if isinstance(import_data[0][i],float):
ks_type_obj['type'] = 'float'
else:
ks_type_obj['type'] = ''
excel_fields.append((ks_type_obj))
return request.make_response(self.from_data(excel_fields,columns_headers,import_data),
headers=[('Content-Disposition',
content_disposition(self.filename(header))),
('Content-Type', self.content_type)],
# cookies={'fileToken': token}
)
class KsChartExcelExport(KsChartExport, http.Controller):
# Excel needs raw data to correctly handle numbers and date values
raw_data = True
@http.route('/ks_dashboard_ninja/export/chart_xls', type='http', auth="user")
def index(self, data):
try:
return self.base(data)
except Exception as exc:
_logger.exception("Exception during request handling.")
payload = json.dumps({
'code': 200,
'message': "Odoo Server Error",
'data': http.serialize_exception(exc)
})
raise InternalServerError(payload) from exc
@property
def content_type(self):
return 'application/vnd.ms-excel'
def filename(self, base):
return base + '.xlsx'
def from_data(self, fields, columns_headers, rows):
with ExportXlsxWriter(fields, columns_headers, len(rows)) as xlsx_writer:
for row_index, row in enumerate(rows):
for cell_index, cell_value in enumerate(row):
xlsx_writer.write_cell(row_index + 1, cell_index, cell_value)
return xlsx_writer.value
class KsChartCsvExport(KsChartExport, http.Controller):
@http.route('/ks_dashboard_ninja/export/chart_csv', type='http', auth="user")
def index(self, data):
try:
return self.base(data)
except Exception as exc:
_logger.exception("Exception during request handling.")
payload = json.dumps({
'code': 200,
'message': "Odoo Server Error",
'data': http.serialize_exception(exc)
})
raise InternalServerError(payload) from exc
@property
def content_type(self):
return 'text/csv;charset=utf8'
def filename(self, base):
return base + '.csv'
def from_data(self, fields,columns_headers, rows):
fp = io.BytesIO()
writer = pycompat.csv_writer(fp, quoting=1)
writer.writerow(columns_headers)
for data in rows:
row = []
for d in data:
# Spreadsheet apps tend to detect formulas on leading =, + and -
if isinstance(d, str) and d.startswith(('=', '-', '+')):
d = "'" + d
row.append(pycompat.to_text(d))
writer.writerow(row)
return fp.getvalue()

View File

@ -0,0 +1,84 @@
import io
import json
import operator
import logging
from odoo import http
from odoo.http import request
from odoo.http import content_disposition,request
from werkzeug.exceptions import InternalServerError
_logger = logging.getLogger(__name__)
class KsDashboardExport(http.Controller):
def base(self, data):
params = json.loads(data)
header, dashboard_data = operator.itemgetter('header', 'dashboard_data')(params)
return request.make_response(self.from_data(dashboard_data),
headers=[('Content-Disposition',
content_disposition(self.filename(header))),
('Content-Type', self.content_type)],
# cookies={'fileToken': token}
)
class KsDashboardJsonExport(KsDashboardExport, http.Controller):
@http.route('/ks_dashboard_ninja/export/dashboard_json', type='http', auth="user")
def index(self, data):
try:
return self.base(data)
except Exception as exc:
_logger.exception("Exception during request handling.")
payload = json.dumps({
'code': 200,
'message': "Odoo Server Error",
'data': http.serialize_exception(exc)
})
raise InternalServerError(payload) from exc
@property
def content_type(self):
return 'text/csv;charset=utf8'
def filename(self, base):
return base + '.json'
def from_data(self, dashboard_data):
fp = io.StringIO()
fp.write(json.dumps(dashboard_data))
return fp.getvalue()
class KsItemJsonExport(KsDashboardExport, http.Controller):
@http.route('/ks_dashboard_ninja/export/item_json', type='http', auth="user")
def index(self, data):
try:
data = json.loads(data)
item_id = data["item_id"]
data['dashboard_data'] = request.env['ks_dashboard_ninja.board'].ks_export_item(item_id)
data = json.dumps(data)
return self.base(data)
except Exception as exc:
_logger.exception("Exception during request handling.")
payload = json.dumps({
'code': 200,
'message': "Odoo Server Error",
'data': http.serialize_exception(exc)
})
raise InternalServerError(payload) from exc
@property
def content_type(self):
return 'text/csv;charset=utf8'
def filename(self, base):
return base + '.json'
def from_data(self, dashboard_data):
fp = io.StringIO()
fp.write(json.dumps(dashboard_data))
return fp.getvalue()

View File

@ -0,0 +1,21 @@
from odoo.addons.web.controllers.domain import Domain
from odoo import http, _
from odoo.http import Controller, request
from odoo.tools.safe_eval import safe_eval
class ksdomainfix(Domain):
# to validate our uid and mycompany based domain
@http.route('/web/domain/validate', type='json', auth="user")
def validate(self, model, domain):
ks_uid_domain = str(domain)
if ks_uid_domain and "%UID" in ks_uid_domain:
ks_domain = ks_uid_domain.replace("%UID", str(request.env.user.id))
return super().validate(model,safe_eval(ks_domain))
elif ks_uid_domain and "%MYCOMPANY" in ks_uid_domain:
ks_domain = ks_uid_domain.replace("%MYCOMPANY", str(request.env.company.id))
return super().validate(model,safe_eval(ks_domain))
else:
return super().validate(model, domain)

View File

@ -0,0 +1,213 @@
import re
import io
import json
import operator
import logging
from odoo.addons.web.controllers.export import ExportXlsxWriter
from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT , xlsxwriter
import datetime
from odoo import http
from odoo.http import content_disposition, request
from odoo.tools import pycompat
from ..common_lib.ks_date_filter_selections import ks_get_date, ks_convert_into_utc, ks_convert_into_local
import os
import pytz
from werkzeug.exceptions import InternalServerError
_logger = logging.getLogger(__name__)
class KsListExport(http.Controller):
def base(self, data):
params = json.loads(data)
# header,list_data = operator.itemgetter('header','chart_data')(params)
header, list_data, item_id, ks_export_boolean, context, params = operator.itemgetter('header', 'chart_data',
'ks_item_id',
'ks_export_boolean',
'context', 'params')(
params)
list_data = json.loads(list_data)
if ks_export_boolean:
item = request.env['ks_dashboard_ninja.item'].browse(int(item_id))
ks_timezone = item._context.get('tz') or item.env.user.tz
if not ks_timezone:
ks_tzone = os.environ.get('TZ')
if ks_tzone:
ks_timezone = ks_tzone
elif os.path.exists('/etc/timezone'):
ks_tzone = open('/etc/timezone').read()
ks_timezone = ks_tzone[0:-1]
try:
datetime.now(pytz.timezone(ks_timezone))
except Exception as e:
_logger.info('Please set the local timezone')
else:
_logger.info('Please set the local timezone')
orderby = item.ks_sort_by_field.id
sort_order = item.ks_sort_by_order
ks_start_date = context.get('ksDateFilterStartDate', False)
ks_end_date = context.get('ksDateFilterEndDate', False)
ksDateFilterSelection = context.get('ksDateFilterSelection', False)
if context.get('allowed_company_ids', False):
item = item.with_context(allowed_company_ids=context.get('allowed_company_ids'))
if item.ks_data_calculation_type == 'query':
query_start_date = item.ks_query_start_date
query_end_date = item.ks_query_end_date
ks_query = str(item.ks_custom_query)
if ks_start_date and ks_end_date:
ks_start_date = datetime.datetime.strptime(ks_start_date,DEFAULT_SERVER_DATETIME_FORMAT)
ks_end_date = datetime.datetime.strptime(ks_end_date,DEFAULT_SERVER_DATETIME_FORMAT)
item = item.with_context(ksDateFilterStartDate=ks_start_date)
item = item.with_context(ksDateFilterEndDate=ks_end_date)
item = item.with_context(ksDateFilterSelection=ksDateFilterSelection)
if item._context.get('ksDateFilterSelection', False):
ks_date_filter_selection = item._context['ksDateFilterSelection']
if ks_date_filter_selection == 'l_custom':
item = item.with_context(ksDateFilterStartDate=ks_start_date)
item = item.with_context(ksDateFilterEndDate=ks_end_date)
item = item.with_context(ksIsDefultCustomDateFilter=False)
else:
ks_date_filter_selection = item.ks_dashboard_ninja_board_id.ks_date_filter_selection
item = item.with_context(ksDateFilterStartDate=item.ks_dashboard_ninja_board_id.ks_dashboard_start_date)
item = item.with_context(ksDateFilterEndDate=item.ks_dashboard_ninja_board_id.ks_dashboard_end_date)
item = item.with_context(ksDateFilterSelection=ks_date_filter_selection)
item = item.with_context(ksIsDefultCustomDateFilter=True)
if ks_date_filter_selection not in ['l_custom', 'l_none']:
ks_date_data = ks_get_date(ks_date_filter_selection, request, 'datetime')
item = item.with_context(ksDateFilterStartDate=ks_date_data["selected_start_date"])
item = item.with_context(ksDateFilterEndDate=ks_date_data["selected_end_date"])
item_domain = params.get('ks_domain_1', [])
ks_chart_domain = item.ks_convert_into_proper_domain(item.ks_domain, item,item_domain)
# list_data = item.ks_fetch_list_view_data(item,ks_chart_domain, ks_export_all=
if list_data['type'] == 'ungrouped':
list_data = item.ks_fetch_list_view_data(item, ks_chart_domain, ks_export_all=True)
elif list_data['type'] == 'grouped':
list_data = item.get_list_view_record(orderby, sort_order, ks_chart_domain, ks_export_all=True)
elif item.ks_data_calculation_type == 'query':
if ks_start_date or ks_end_date:
query_start_date = ks_start_date
query_end_date = ks_end_date
ks_query_result = item.ks_get_list_query_result(ks_query, query_start_date, query_end_date, ks_offset=0,
ks_export_all=True)
list_data = item.ks_format_query_result(ks_query_result)
# chart_data['labels'].insert(0,'Measure')
columns_headers = list_data['label']
import_data = []
for dataset in list_data['data_rows']:
if not list_data['type'] == 'grouped':
for count, index in enumerate(dataset['ks_column_type']):
if index == 'datetime':
ks_converted_date = False
date_string = dataset['data'][count]
if dataset['data'][count]:
ks_converted_date = ks_convert_into_local(datetime.datetime.strptime(date_string, '%m/%d/%y %H:%M:%S'),ks_timezone)
dataset['data'][count] = ks_converted_date
for ks_count, val in enumerate(dataset['data']):
if isinstance(val, (float, int)):
if val >= 0:
try:
ks_precision = item.sudo().env.ref('ks_dashboard_ninja.ks_dashboard_ninja_precision').digits
except Exception as e:
ks_precision = 2
dataset['data'][ks_count] = item.env['ir.qweb.field.float'].sudo().value_to_html(val,
{'precision': ks_precision})
import_data.append(dataset['data'])
excel_fields = []
for i in range(len(columns_headers)):
ks_type_obj = {}
if (len(import_data)):
if isinstance(import_data[0][i], float):
ks_type_obj['type'] = 'float'
else:
ks_type_obj['type'] = ''
excel_fields.append((ks_type_obj))
return request.make_response(self.from_data(excel_fields,columns_headers, import_data),
headers=[('Content-Disposition',
content_disposition(self.filename(header))),
('Content-Type', self.content_type)],
# cookies={'fileToken': token}
)
class KsListExcelExport(KsListExport, http.Controller):
# Excel needs raw data to correctly handle numbers and date values
raw_data = True
@http.route('/ks_dashboard_ninja/export/list_xls', type='http', auth="user")
def index(self, data):
try:
return self.base(data)
except Exception as exc:
_logger.exception("Exception during request handling.")
payload = json.dumps({
'code': 200,
'message': "Odoo Server Error",
'data': http.serialize_exception(exc)
})
raise InternalServerError(payload) from exc
@property
def content_type(self):
return 'application/vnd.ms-excel'
def filename(self, base):
return base + '.xlsx'
def from_data(self, fields, columns_headers, rows):
with ExportXlsxWriter(fields, columns_headers, len(rows)) as xlsx_writer:
for row_index, row in enumerate(rows):
for cell_index, cell_value in enumerate(row):
xlsx_writer.write_cell(row_index + 1, cell_index, cell_value)
return xlsx_writer.value
class KsListCsvExport(KsListExport, http.Controller):
@http.route('/ks_dashboard_ninja/export/list_csv', type='http', auth="user")
def index(self, data):
try:
return self.base(data)
except Exception as exc:
_logger.exception("Exception during request handling.")
payload = json.dumps({
'code': 200,
'message': "Odoo Server Error",
'data': http.serialize_exception(exc)
})
raise InternalServerError(payload) from exc
@property
def content_type(self):
return 'text/csv;charset=utf8'
def filename(self, base):
return base + '.csv'
def from_data(self, fields, column_headers,rows):
fp = io.BytesIO()
writer = pycompat.csv_writer(fp, quoting=1)
writer.writerow(column_headers)
for data in rows:
row = []
for d in data:
# Spreadsheet apps tend to detect formulas on leading =, + and -
if isinstance(d, str) and d.startswith(('=', '-', '+')):
d = "'" + d
row.append(pycompat.to_text(d))
writer.writerow(row)
return fp.getvalue()

View File

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

View File

@ -0,0 +1,605 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Default Templates -->
<record id="ks_blank" model="ks_dashboard_ninja.board_template">
<field name="name">Blank</field>
<field name="ks_item_count">0</field>
</record>
<record id="ks_template_1" model="ks_dashboard_ninja.board_template">
<field name="name">Template 1</field>
<field name="ks_gridstack_config">[
{"item_id":"ks_dashboard_ninja.ks_default_item_1", "data": {"x": 0, "y": 10, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_2", "data": {"x": 0, "y": 8, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_3", "data": {"x": 3, "y": 0, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_4", "data": {"x": 0, "y": 2, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_5", "data": {"x": 6, "y": 12, "w": 6, "h": 6}},
{"item_id":"ks_dashboard_ninja.ks_default_item_6", "data": {"x": 0, "y": 28, "w": 12, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_7", "data": {"x": 0, "y": 43, "w": 5, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_8", "data": {"x": 6, "y": 6, "w": 6, "h": 6}},
{"item_id":"ks_dashboard_ninja.ks_default_item_9", "data": {"x": 5, "y": 36, "w": 7, "h": 7}},
{"item_id":"ks_dashboard_ninja.ks_default_item_10", "data": {"x": 4, "y": 23, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_11", "data": {"x": 6, "y": 18, "w": 6, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_12", "data": {"x": 0, "y": 6, "w": 3, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_13", "data": {"x": 3, "y": 8, "w": 3, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_15", "data": {"x": 0, "y": 18, "w": 6, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_16", "data": {"x": 0, "y": 0, "w": 3, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_17", "data": {"x": 3, "y": 6, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_18", "data": {"x": 3, "y": 4, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_19", "data": {"x": 3, "y": 10, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_20", "data": {"x": 5, "y": 43, "w": 7, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_21", "data": {"x": 0, "y": 12, "w": 6, "h":
6}},
{"item_id":"ks_dashboard_ninja.ks_default_item_22", "data": {"x": 0, "y": 36, "w": 5, "h":
7}},
{"item_id":"ks_dashboard_ninja.ks_default_item_23", "data": {"x": 0, "y": 32, "w": 12, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_24", "data": {"x": 8, "y": 23, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_25", "data": {"x": 0, "y": 23, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_26", "data": {"x": 0, "y": 4, "w": 3, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_27", "data": {"x": 3, "y": 3, "w": 3, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_28", "data": {"x": 6, "y": 0, "w": 6, "h":
6}}
]
</field>
<field name="ks_item_count">7</field>
</record>
<record id="ks_template_2" model="ks_dashboard_ninja.board_template">
<field name="name">Template 2</field>
<field name="ks_gridstack_config">[
{"item_id":"ks_dashboard_ninja.ks_default_item_1", "data": {"x": 0, "y": 0, "w": 2, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_2", "data": {"x": 4, "y": 0, "w": 2, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_3", "data": {"x": 2, "y": 0, "w": 2, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_4", "data": {"x": 8, "y": 0, "w": 2, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_5", "data": {"x": 4, "y": 18, "w": 8, "h": 5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_6", "data": {"x": 8, "y": 27, "w": 4, "h":
6}},
{"item_id":"ks_dashboard_ninja.ks_default_item_7", "data": {"x": 0, "y": 18, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_8", "data": {"x": 4, "y": 27, "w": 4, "h":
6}},
{"item_id":"ks_dashboard_ninja.ks_default_item_9", "data": {"x": 4, "y": 13, "w": 8, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_10", "data": {"x": 0, "y": 23, "w": 4, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_11", "data": {"x": 0, "y": 4, "w": 4, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_12", "data": {"x": 6, "y": 0, "w": 2, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_13", "data": {"x": 10, "y": 2, "w": 2, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_15", "data": {"x":0, "y": 33, "w": 6, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_16", "data": {"x": 2, "y": 2, "w": 2, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_17", "data": {"x": 8, "y": 2, "w": 2, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_18", "data": {"x": 6, "y": 2, "w": 2, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_19", "data": {"x": 0, "y": 2, "w": 2, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_20", "data": {"x": 4, "y": 8, "w": 8, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_21", "data": {"x": 0, "y": 13, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_22", "data": {"x": 4, "y": 23, "w": 8, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_23", "data": {"x": 6, "y": 33, "w": 6, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_24", "data": {"x": 4, "y": 4, "w": 8, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_25", "data": {"x": 0, "y": 8, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_26", "data": {"x": 4, "y": 2, "w": 2, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_27", "data": {"x": 10, "y": 2, "w": 2, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_28", "data": {"x": 0, "y": 27, "w": 4, "h":
6}}
]
</field>
<field name="ks_item_count">7</field>
</record>
<record id="ks_template_3" model="ks_dashboard_ninja.board_template">
<field name="name">Template 3</field>
<field name="ks_gridstack_config">[
{"item_id":"ks_dashboard_ninja.ks_default_item_1", "data": {"x": 0, "y": 0, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_2", "data": {"x": 6, "y": 0, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_3", "data": {"x": 3, "y": 0, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_4", "data": {"x": 0, "y": 2, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_5", "data": {"x": 7, "y": 2, "w": 5, "h": 4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_6", "data": {"x": 0, "y": 28, "w": 12, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_7", "data": {"x": 4, "y": 14, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_8", "data": {"x": 0, "y": 33, "w": 3, "h": 5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_9", "data": {"x": 8, "y": 23, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_10", "data": {"x": 8, "y": 14, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_11", "data": {"x": 0, "y": 23, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_12", "data": {"x": 9, "y": 0, "w": 3, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_13", "data": {"x": 3, "y": 2, "w": 4, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_15", "data": {"x":0, "y": 19, "w": 12, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_16", "data": {"x": 0, "y": 8, "w": 3, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_17", "data": {"x": 3, "y": 4, "w": 4, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_18", "data": {"x": 0, "y": 12, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_19", "data": {"x": 0, "y": 4, "w": 3, "h": 2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_20", "data": {"x": 3, "y": 6, "w": 9, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_21", "data": {"x": 0, "y": 14, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_22", "data": {"x": 6, "y": 33, "w": 6, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_23", "data": {"x": 0, "y": 19, "w": 12, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_24", "data": {"x": 3, "y": 10, "w": 9, "h":
4}},
{"item_id":"ks_dashboard_ninja.ks_default_item_25", "data": {"x": 4, "y": 23, "w": 4, "h":
5}},
{"item_id":"ks_dashboard_ninja.ks_default_item_26", "data": {"x": 0, "y": 8, "w": 3, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_27", "data": {"x": 0, "y": 6, "w": 3, "h":
2}},
{"item_id":"ks_dashboard_ninja.ks_default_item_28", "data": {"x": 3, "y": 33, "w": 3, "h":
5}}
]
</field>
<field name="ks_item_count">7</field>
</record>
<!--Default items (7 right now) created here that will be used for default templates in future dashboards-->
<record id="ks_default_item_1" model="ks_dashboard_ninja.item">
<field name="name">Tile (layout 1)</field>
<field name="ks_dashboard_item_type">ks_tile</field>
<field name="ks_record_count_type">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_domain">[["id","&gt;",150]]</field>
<field name="ks_default_icon">bar-chart</field>
<field name="ks_dashboard_item_theme">blue</field>
<field name="ks_background_color">#FFE2E5,0.99</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_layout">layout1</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_2" model="ks_dashboard_ninja.item">
<field name="name">Tile (layout 3)</field>
<field name="ks_dashboard_item_type">ks_tile</field>
<field name="ks_record_count_type">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_default_icon">users</field>
<field name="ks_dashboard_item_theme">red</field>
<field name="ks_background_color">#FFF4DE,0.99</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_layout">layout3</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_3" model="ks_dashboard_ninja.item">
<field name="name">Tile (layout 2)</field>
<field name="ks_dashboard_item_type">ks_tile</field>
<field name="ks_record_count_type">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_domain">[["id","&lt;",50]]</field>
<field name="ks_default_icon">money</field>
<field name="ks_dashboard_item_theme">green</field>
<field name="ks_background_color">#DCFCE7,0.99</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_layout">layout2</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_4" model="ks_dashboard_ninja.item">
<field name="name">Tile (layout 5)</field>
<field name="ks_dashboard_item_type">ks_tile</field>
<field name="ks_record_count_type">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_domain">[["id","&lt;",100]]</field>
<field name="ks_default_icon">paper-plane</field>
<field name="ks_dashboard_item_theme">yellow</field>
<field name="ks_background_color">#F3E8FF,0.99</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_layout">layout5</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_5" model="ks_dashboard_ninja.item">
<field name="name">Bar Chart</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<!-- <field name="ks_chart_measure_field" eval="[ref('base.field_res_country__phone_code')]"/>-->
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__currency_id')"/>
<field name="ks_domain">[["id","&lt;",40]]</field>
<field name="ks_chart_item_color">dark</field>
<field name="ks_dashboard_item_type">ks_bar_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_6" model="ks_dashboard_ninja.item">
<field name="name">Line Chart</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<!-- <field name="ks_chart_measure_field" eval="[ref('base.field_res_country__phone_code')]"/>-->
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__currency_id')"/>
<field name="ks_domain">[["id","&lt;",10]]</field>
<field name="ks_chart_item_color">dark</field>
<field name="ks_dashboard_item_type">ks_line_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_7" model="ks_dashboard_ninja.item">
<field name="name">Pie Chart</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__currency_id')"/>
<field name="ks_domain">[["id","&lt;",10]]</field>
<field name="ks_dashboard_item_type">ks_pie_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_8" model="ks_dashboard_ninja.item">
<field name="name">list view (Un-Grouped)</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_list_view_type">grouped</field>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__phone_code')"/>
<field name="ks_list_view_group_fields" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_domain">[["id","&lt;",10]]</field>
<field name="ks_dashboard_item_type">ks_list_view</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_9" model="ks_dashboard_ninja.item">
<field name="name">Horizontal Bar</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__currency_id')"/>
<field name="ks_domain">[["id","&lt;",10]]</field>
<field name="ks_chart_item_color">material</field>
<field name="ks_dashboard_item_type">ks_horizontalBar_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_10" model="ks_dashboard_ninja.item">
<field name="name">Polar Area</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__currency_id')"/>
<field name="ks_domain">[["id","&lt;",10]]</field>
<field name="ks_chart_item_color">moonrise</field>
<field name="ks_dashboard_item_type">ks_polarArea_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_11" model="ks_dashboard_ninja.item">
<field name="name">Doughnut chart</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__name')"/>
<field name="ks_domain">[["id","&lt;",10]]</field>
<field name="ks_chart_item_color">moonrise</field>
<field name="ks_record_data_limit">100</field>
<field name="ks_show_data_value">1</field>
<field name="ks_unit_selection">monetary</field>
<field name="ks_dashboard_item_type">ks_doughnut_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_12" model="ks_dashboard_ninja.item">
<field name="name">Tile (layout 4)</field>
<field name="ks_dashboard_item_type">ks_tile</field>
<field name="ks_record_count_type">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_domain">[["id","&lt;",50]]</field>
<field name="ks_default_icon">shopping-cart</field>
<field name="ks_dashboard_item_theme">red</field>
<field name="ks_background_color">#FFE2E5,0.99</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_layout">layout4</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_13" model="ks_dashboard_ninja.item">
<field name="name">Tile (layout 6)</field>
<field name="ks_dashboard_item_type">ks_tile</field>
<field name="ks_record_count_type">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_domain">[["id","&lt;",100]]</field>
<field name="ks_default_icon">car</field>
<field name="ks_dashboard_item_theme">red</field>
<field name="ks_background_color">#FFF4DE,0.53</field>
<field name="ks_font_color">#000000,0.70</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_layout">layout6</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_14" model="ks_dashboard_ninja.item">
<field name="name">Pie Chart</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__currency_id')"/>
<field name="ks_domain">[["id","&lt;",10]]</field>
<field name="ks_chart_item_color">dark</field>
<field name="ks_dashboard_item_type">ks_pie_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_15" model="ks_dashboard_ninja.item">
<field name="name">Area Chart</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__code')"/>
<field name="ks_chart_item_color">default</field>
<field name="ks_dashboard_item_type">ks_area_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
</data>
<record id="ks_default_item_16" model="ks_dashboard_ninja.item">
<field name="name">Kpi Ratio</field>
<field name="ks_dashboard_item_type">ks_kpi</field>
<field name="ks_record_count_type">count</field>
<field name="ks_record_count_type_2">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_model_id_2" eval="ref('base.model_res_country')"/>
<field name="ks_data_comparison">Ratio</field>
<field name="ks_domain">[["id","&lt;",100]]</field>
<field name="ks_default_icon">user</field>
<field name="ks_dashboard_item_theme">blue</field>
<field name="ks_background_color">#DCFCE7,0.99</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_17" model="ks_dashboard_ninja.item">
<field name="name">Kpi ( Percentage)</field>
<field name="ks_dashboard_item_type">ks_kpi</field>
<field name="ks_record_count_type">count</field>
<field name="ks_record_count_type_2">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_model_id_2" eval="ref('base.model_res_country')"/>
<field name="ks_domain">[["id","&lt;",100]]</field>
<field name="ks_data_comparison">Percentage</field>
<field name="ks_default_icon">paper-plane</field>
<field name="ks_dashboard_item_theme">red</field>
<field name="ks_background_color">#F3E8FF,0.99</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_18" model="ks_dashboard_ninja.item">
<field name="name">Kpi ( Number)</field>
<field name="ks_dashboard_item_type">ks_kpi</field>
<field name="ks_record_count_type">count</field>
<field name="ks_record_count_type_2">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_model_id_2" eval="ref('base.model_res_country')"/>
<field name="ks_target_view">Number</field>
<field name="ks_goal_enable">1</field>
<field name="ks_domain">[["id","&lt;",100]]</field>
<field name="ks_data_comparison">Sum</field>
<field name="ks_default_icon">money</field>
<field name="ks_dashboard_item_theme">green</field>
<field name="ks_background_color">#F3E8FF,0.63</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_19" model="ks_dashboard_ninja.item">
<field name="name">Kpi (sum)</field>
<field name="ks_dashboard_item_type">ks_kpi</field>
<field name="ks_record_count_type">count</field>
<field name="ks_record_count_type_2">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_model_id_2" eval="ref('base.model_res_country')"/>
<field name="ks_data_comparison">Sum</field>
<field name="ks_domain">[["id","&lt;",100]]</field>
<field name="ks_default_icon">bar-chart</field>
<field name="ks_dashboard_item_theme">yellow</field>
<field name="ks_background_color">#FFF4DE,0.99</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_20" model="ks_dashboard_ninja.item">
<field name="name">Bar Chart With Data Values</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__name')"/>
<field name="ks_domain">[["id","&lt;",40]]</field>
<field name="ks_chart_item_color">default</field>
<field name="ks_dashboard_item_type">ks_bar_chart</field>
<field name="ks_show_data_value">1</field>
<field name="ks_unit_selection">monetary</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_21" model="ks_dashboard_ninja.item">
<field name="name">Semi Circle Pie Chart</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__name')"/>
<field name="ks_semi_circle_chart">1</field>
<field name="ks_chart_item_color">material</field>
<field name="ks_record_data_limit">10</field>
<field name="ks_show_data_value">1</field>
<field name="ks_unit_selection">monetary</field>
<field name="ks_dashboard_item_type">ks_pie_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_22" model="ks_dashboard_ninja.item">
<field name="name">Horizontal Bar(sub-group)</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__name')"/>
<field name="ks_chart_relation_sub_groupby" eval="ref('base.field_res_country__name')"/>
<field name="ks_chart_item_color">default</field>
<field name="ks_domain">[["id","&lt;",10]]</field>
<field name="ks_show_data_value">1</field>
<field name="ks_unit_selection">monetary</field>
<field name="ks_dashboard_item_type">ks_horizontalBar_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_23" model="ks_dashboard_ninja.item">
<field name="name">Area Chart with data values</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__code')"/>
<field name="ks_chart_item_color">material</field>
<field name="ks_record_data_limit">25</field>
<field name="ks_show_data_value">1</field>
<field name="ks_unit_selection">monetary</field>
<field name="ks_dashboard_item_type">ks_area_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_24" model="ks_dashboard_ninja.item">
<field name="name">Line Chart with values</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__name')"/>
<field name="ks_chart_item_color">moonrise</field>
<field name="ks_record_data_limit">10</field>
<field name="ks_show_data_value">1</field>
<field name="ks_unit_selection">monetary</field>
<field name="ks_dashboard_item_type">ks_line_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_25" model="ks_dashboard_ninja.item">
<field name="name">Doughnut semi circle</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_measure_field" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__name')"/>
<field name="ks_chart_item_color">default</field>
<field name="ks_semi_circle_chart">1</field>
<field name="ks_record_data_limit">25</field>
<field name="ks_show_data_value">1</field>
<field name="ks_unit_selection">monetary</field>
<field name="ks_dashboard_item_type">ks_doughnut_chart</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_26" model="ks_dashboard_ninja.item">
<field name="name">Kpi 26(Average)</field>
<field name="ks_dashboard_item_type">ks_kpi</field>
<field name="ks_record_field" eval="ref('base.field_res_country__name')"/>
<field name="ks_record_field_2" eval="ref('base.field_res_country__name')"/>
<field name="ks_data_format">indian</field>
<field name="ks_record_count_type">average</field>
<field name="ks_record_count_type_2">average</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_model_id_2" eval="ref('base.model_res_country')"/>
<field name="ks_target_view">Number</field>
<field name="ks_goal_enable">1</field>
<field name="ks_domain">[["id","&lt;",100]]</field>
<field name="ks_data_comparison">Sum</field>
<field name="ks_default_icon">money</field>
<field name="ks_dashboard_item_theme">blue</field>
<field name="ks_background_color">#DCFCE7,0.99</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_27" model="ks_dashboard_ninja.item">
<field name="name">Kpi (previous)</field>
<field name="ks_dashboard_item_type">ks_kpi</field>
<field name="ks_record_count_type">count</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_domain">[["id","&lt;",100]]</field>
<field name="ks_previous_period">1</field>
<field name="ks_date_filter_selection">t_week</field>
<field name="ks_default_icon">money</field>
<field name="ks_dashboard_item_theme">green</field>
<field name="ks_background_color">#FFE2E5,0.59</field>
<field name="ks_font_color">#000000,0.99</field>
<field name="ks_default_icon_color">#000000,0.99</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_28" model="ks_dashboard_ninja.item">
<field name="name">list view (grouped)</field>
<field name="ks_chart_data_count_type">sum</field>
<field name="ks_chart_groupby_type">relational_type</field>
<field name="ks_model_id" eval="ref('base.model_res_country')"/>
<field name="ks_chart_relation_groupby" eval="ref('base.field_res_country__name')"/>
<field name="ks_list_view_type">ungrouped</field>
<field name="ks_list_view_group_fields" eval="[(6, 0, [ref('base.field_res_country__phone_code')])]"/>
<field name="ks_list_view_fields"
eval="[(6, 0, [ref('base.field_res_country__phone_code'),ref('base.field_res_country__name')])]"/>
<field name="ks_domain">[["id","&lt;",10]]</field>
<field name="ks_dashboard_item_type">ks_list_view</field>
<field name="ks_company_id" eval="0"/>
</record>
<record id="ks_default_item_10_action" model="ks_dashboard_ninja.item_action">
<field name="ks_dashboard_item_id" ref="ks_default_item_10"/>
<field name="ks_chart_type">ks_bar_chart</field>
<field name="ks_item_action_field" ref='base.field_res_country__phone_code'/>
</record>
<record id="ks_default_item_10_action1" model="ks_dashboard_ninja.item_action">
<field name="ks_dashboard_item_id" ref="ks_default_item_10"/>
<field name="ks_chart_type">ks_pie_chart</field>
<field name="ks_item_action_field" ref='base.field_res_country__name'/>
</record>
<!-- Default dashboard Data -->
<data noupdate="1">
<record forcecreate="True" id="ks_dashboard_ninja_precision" model="decimal.precision">
<field name="name">Dashboard Ninja Decimal Precision</field>
<field name="digits" eval="2"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="ir_cron_send_target_email" model="ir.cron">
<field name="name">Kpi mail cron</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="model_id" ref="model_ks_dashboard_ninja_item"/>
<field name="code">model.check_target()</field>
<field name="state">code</field>
</record>
</odoo>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="ks_dashboard_item_seq" model="ir.sequence">
<field name="name">Dashboard Seq</field>
<field name="code">ks_dashboard_ninja.item</field>
<field name="padding">2</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Three Default Demo Dashboard with Templates : Template1, Template2, Template3-->
<record id="demo_template1_dashboard" model="ks_dashboard_ninja.board">
<field name="name">Template1 Dashboard</field>
<field name="ks_dashboard_menu_name">模版1</field>
<field name="ks_dashboard_top_menu_id" eval="ref('ks_dashboard_ninja.dashboards_menu_root')"/>
<field name="ks_dashboard_default_template" eval="ref('ks_dashboard_ninja.ks_template_1')"/>
<field name="ks_dashboard_active">1</field>
<field name="ks_dashboard_group_access" eval="False"/>
</record>
<record id="ks_my_default_dashboard_board" model="ks_dashboard_ninja.board">
<field name="name">My Dashboard</field>
<field name="ks_dashboard_state">Locked</field>
<field name="ks_dashboard_menu_name">My Dashboard</field>
<field name="ks_dashboard_top_menu_id" eval="ref('ks_dashboard_ninja.dashboards_menu_root')"/>
<field name="ks_dashboard_active">1</field>
<field name="ks_dashboard_default_template" ref="ks_dashboard_ninja.ks_blank"/>
<field name="ks_dashboard_group_access" eval="False"/>
</record>
<record id="demo_template2_dashboard" model="ks_dashboard_ninja.board">
<field name="name">Template2 Dashboard</field>
<field name="ks_dashboard_menu_name">模版2</field>
<field name="ks_dashboard_top_menu_id" eval="ref('ks_dashboard_ninja.dashboards_menu_root')"/>
<field name="ks_dashboard_default_template" eval="ref('ks_dashboard_ninja.ks_template_2')"/>
<field name="ks_dashboard_active">1</field>
<field name="ks_dashboard_group_access" eval="False"/>
</record>
<record id="demo_template3_dashboard" model="ks_dashboard_ninja.board">
<field name="name">Template3 Dashboard</field>
<field name="ks_dashboard_menu_name">模版3</field>
<field name="ks_dashboard_top_menu_id" eval="ref('ks_dashboard_ninja.dashboards_menu_root')"/>
<field name="ks_dashboard_default_template" eval="ref('ks_dashboard_ninja.ks_template_3')"/>
<field name="ks_dashboard_active">1</field>
<field name="ks_dashboard_group_access" eval="False"/>
</record>
</data>
</odoo>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
from odoo import models, fields, api, _
class KpSendMail(models.Model):
_name = 'ks_dashboard_ninja.kpi_mail'
_description = 'Dashboard Ninja Kpi mail'
name = fields.Char(string="Email To:")

View File

@ -0,0 +1,16 @@
from . import ks_dashboard_ninja
from . import ks_dashboard_ninja_items
from . import ks_item_action
from . import ks_child_dashboard
from . import ks_dashboard_filters
from . import ks_dashboard_templates
from . import ks_dn_to_do_item
from . import ks_import_dashboard
from . import Kpi_mail
from . import res_settings
from . import ks_ai_ninja_dashboard
from . import ks_ai_whole_dashboard
from . import ks_key_fetch
from . import ks_chat_channel

View File

@ -0,0 +1,397 @@
import base64
import io
import json
import logging
# import osgit
from urllib.parse import quote
import pandas as pd
import requests
from gtts import gTTS
from odoo.exceptions import ValidationError
from odoo.tools import config
from odoo import api, fields, models, _
_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 ID',
domain="[('access_ids','!=',False),('transient','=',False),"
"('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. ")
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 val['name'] != 'id' and val['name'] != 'sequence' and val['store'] == True}
ks_fields_name = {val['name']:val['type'] for val in ks_filtered_fields.values()}
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,
'type': self.ks_type,
'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 Dashboards')])[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': 'Generate items with AI',
'params': {'ks_dashboard_id': ks_create_record.id, 'explain_ai_whole': True},
'tag': 'ks_ai_dashboard_ninja',
'context': context,
'target':'new'
}
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 val[
'name'] != 'id' and val['name'] != 'sequence' and val['store'] == True}
ks_fields_name = {val['name']: val['type'] for val in ks_filtered_fields.values()}
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"))
@api.model
def ks_generate_analysis(self, ks_items_explain, ks_rest_items, dashboard_id):
if ks_items_explain:
result = []
api_key = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.dn_api_key')
ks_url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url')
words = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.ks_analysis_word_length')
url = ks_url + "/api/v1/ks_dn_main_api"
for i in range(0, len(ks_items_explain)):
if api_key and url:
json_data = {'name': api_key,
'items': json.dumps(ks_items_explain[i]),
'type': 'ks_ai_explain',
'url': self.env['ir.config_parameter'].sudo().get_param('web.base.url'),
'db_name': self.env.cr.dbname,
'words': words if words else 100
}
ks_response = requests.post(url, data=json_data)
if ks_response.status_code == 200 and json.loads(ks_response.text):
ks_ai_response = json.loads(ks_response.text)
item = ks_ai_response[0]
if item['analysis'] or item['insights']:
try:
self.env['ks_dashboard_ninja.item'].browse(item['id']).write({
'ks_ai_analysis': item['analysis'] + 'ks_gap' + item['insights']
})
result.append(True)
except:
result
else:
result
else:
result
else:
raise ValidationError(_("Please put API key and URL"))
if len(result):
if self.env.context.get('explain_items_with_ai', False):
self.env['ks_dashboard_ninja.board'].browse(dashboard_id).write({
'ks_ai_explain_dash': False
})
else:
self.env['ks_dashboard_ninja.board'].browse(dashboard_id).write({
'ks_ai_explain_dash': True
})
return True
else:
raise ValidationError(_("AI Responds with the wrong analysis. Please try again "))
elif ks_rest_items:
if self.env.context.get('explain_items_with_ai', False):
self.env['ks_dashboard_ninja.board'].browse(dashboard_id).write({
'ks_ai_explain_dash': False
})
else:
self.env['ks_dashboard_ninja.board'].browse(dashboard_id).write({
'ks_ai_explain_dash': True
})
return True
else:
return False
def get_ai_explain(self, item_id):
res = self.env['ks_dashboard_ninja.item'].browse(item_id).ks_ai_analysis
return res
@api.model
def ks_switch_default_dashboard(self, dashboard_id):
self.env['ks_dashboard_ninja.board'].browse(dashboard_id).write({
'ks_ai_explain_dash': False
})
return True
@api.model
def ks_generatetext_to_speech(self, item_id):
if (item_id):
try:
ks_text = self.env['ks_dashboard_ninja.item'].browse(item_id).ks_ai_analysis
if ks_text:
language = 'en'
ks_myobj = gTTS(text=ks_text, lang=language, slow=False)
audio_data = io.BytesIO()
ks_myobj.write_to_fp(audio_data)
audio_data.seek(0)
binary_data = audio_data.read()
wav_file = base64.b64encode(binary_data).decode('UTF-8')
data = {"snd": wav_file}
return json.dumps(data)
else:
return False
except Exception as e:
_logger.error(e)
raise ValidationError(_("Some problem in audio generation."))
else:
return False
@api.model
def ks_gen_chat_res(self,**kwargs):
ks_question = kwargs.get('ks_question')
url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url') + "/api/v1/get_sql_query"
data = {
"question": ks_question,
}
try:
ks_response = requests.post(url,data=data)
if (ks_response.status_code == 200):
ks_response = json.loads(ks_response.text)['response']['Query']
return self.ks_gen_dataframe(ks_response,ks_question)
else:
return False
except Exception as e:
_logger.error(e)
return False
def ks_gen_dataframe(self,ks_query,question):
host = config.get('db_host', False)
user = quote(config.get('db_user', False))
port = config.get('db_port', False) or 5432
password = quote(config.get('db_password', False))
db = config.get('db_name', False) or self.env.cr.dbname
if not all([host, user, port, password, db]):
return False
else:
sql_uri = f"postgresql+psycopg2://{user}:{password}@{host}:{port}/{db}"
ks_fixed_url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url') + "/api/v1/get_fixed_query"
try:
df = pd.read_sql(ks_query, sql_uri)
except Exception as e:
ks_query_data = {
'query':ks_query,
'error':e
}
fixed_query = requests.post(ks_fixed_url, data=ks_query_data)
if fixed_query.status_code == 200:
ks_corrected_query = fixed_query.text
df = pd.read_sql(ks_corrected_query, sql_uri)
else:
return False
if any(df.dtypes == 'datetime64[ns]'):
datetime_columns = [col for col in df.columns if df[col].dtype == 'datetime64[ns]']
df[datetime_columns] = df[datetime_columns].astype(str)
# Convert DataFrame to JSON
if len(df) >= 100:
df = df.head(100)
partial_data = True
df_json = df.to_json(orient='records')
ans = "As dataframe having more data to analyse we are not showing dataframe summary"
# Generate answer
if len(df) < 13:
ks_ans_url = self.env['ir.config_parameter'].sudo().get_param(
'ks_dashboard_ninja.url') + "/api/v1/get_answer"
ks_ans_data = {'df':df.to_dict(orient='records'),'question':question}
ans = requests.post(ks_ans_url, json = ks_ans_data)
if ans.status_code == 200:
ans = ans.text
response_json = {
"Dataframe": df_json,
"Answer": ans,
}
else:
return False
else:
response_json = {
"Dataframe": df_json,
"Answer": ans,
}
return response_json

View File

@ -0,0 +1,90 @@
import json
import logging
import requests
from odoo.exceptions import ValidationError
from odoo import fields, models, _
_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 Dashboards')])[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 val['name'] != 'id' and val['name'] != 'sequence' and val['store'] == True}
ks_fields_name = {val['name']:val['type'] for val in ks_filtered_fields.values()}
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',
'tag': 'reload',
}
else:
self.env['ks_dashboard_ninja.board'].browse(ks_dash_id).unlink()
raise ValidationError(_("Items didn't render, please try again!"))
else:
raise ValidationError(_("AI Responds with the following status:- %s") % ks_ai_response.text)
else:
raise ValidationError(_("Please enter URL and API Key in General Settings"))
else:
raise ValidationError(_("Please enter the Model"))

View File

@ -0,0 +1,29 @@
from odoo import models, fields, api, _
class ChatChannel(models.Model):
_inherit = 'discuss.channel'
ks_dashboard_board_id = fields.Many2one('ks_dashboard_ninja.board')
ks_dashboard_item_id = fields.Many2one('ks_dashboard_ninja.item')
def ks_chat_wizard_channel_id(self, **kwargs):
item_id = kwargs.get('item_id')
dashboard_id = kwargs.get('dashboard_id')
item_name = kwargs.get('item_name')
dashboard_name = kwargs.get('dashboard_name')
channel = self.search([('ks_dashboard_item_id', '=', item_id)], limit=1)
if not channel:
users = self.env['res.users'].search([
('active', '=', True), ('groups_id', 'in', self.env.ref('base.group_user').id)]).mapped('partner_id.id')
channel = self.sudo().create({
'name': f"{dashboard_name} - {item_name}",
'ks_dashboard_board_id': dashboard_id,
'ks_dashboard_item_id': item_id,
'channel_member_ids': [(0, 0, {'partner_id': partner_id}) for partner_id in users]
})
return channel.id if channel else None

View File

@ -0,0 +1,25 @@
from odoo import models, fields, api, _
class KsDashboardNinjaBoardItemAction(models.Model):
_name = 'ks_dashboard_ninja.child_board'
_description = 'Dashboard Ninja Child Board'
name = fields.Char()
ks_dashboard_ninja_id = fields.Many2one("ks_dashboard_ninja.board", string="Select Dashboard")
ks_gridstack_config = fields.Char('Item Configurations')
# ks_board_active_user_ids = fields.Many2many('res.users')
ks_active = fields.Boolean("Is Selected")
ks_dashboard_menu_name = fields.Char(string="Menu Name", related='ks_dashboard_ninja_id.ks_dashboard_menu_name', store=True)
board_type = fields.Selection([('default', 'Default'), ('child', 'Child')])
company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
ks_computed_group_access = fields.Many2many('res.groups', compute='_compute_ks_computed_group_access', store=True)
@api.depends('ks_dashboard_ninja_id.ks_dashboard_group_access')
def _compute_ks_computed_group_access(self):
for record in self:
record.ks_computed_group_access = record.ks_dashboard_ninja_id.ks_dashboard_group_access
def write(self,vals):
return super(KsDashboardNinjaBoardItemAction, self).write(vals)

View File

@ -0,0 +1,182 @@
country = {
'AF': ('Afghanistan', (60.5284298033, 29.318572496, 75.1580277851, 38.4862816432)),
'AO': ('Angola', (11.6400960629, -17.9306364885, 24.0799052263, -4.43802336998)),
'AL': ('Albania', (19.3044861183, 39.624997667, 21.0200403175, 42.6882473822)),
'AE': ('United Arab Emirates', (51.5795186705, 22.4969475367, 56.3968473651, 26.055464179)),
'AR': ('Argentina', (-73.4154357571, -55.25, -53.628348965, -21.8323104794)),
'AM': ('Armenia', (43.5827458026, 38.7412014837, 46.5057198423, 41.2481285671)),
'AQ': ('Antarctica', (-180.0, -90.0, 180.0, -63.2706604895)),
'TF': ('Fr. S. and Antarctic Lands', (68.72, -49.775, 70.56, -48.625)),
'AU': ('Australia', (113.338953078, -43.6345972634, 153.569469029, -10.6681857235)),
'AT': ('Austria', (9.47996951665, 46.4318173285, 16.9796667823, 49.0390742051)),
'AZ': ('Azerbaijan', (44.7939896991, 38.2703775091, 50.3928210793, 41.8606751572)),
'BI': ('Burundi', (29.0249263852, -4.49998341229, 30.752262811, -2.34848683025)),
'BE': ('Belgium', (2.51357303225, 49.5294835476, 6.15665815596, 51.4750237087)),
'BJ': ('Benin', (0.772335646171, 6.14215770103, 3.79711225751, 12.2356358912)),
'BF': ('Burkina Faso', (-5.47056494793, 9.61083486576, 2.17710778159, 15.1161577418)),
'BD': ('Bangladesh', (88.0844222351, 20.670883287, 92.6727209818, 26.4465255803)),
'BG': ('Bulgaria', (22.3805257504, 41.2344859889, 28.5580814959, 44.2349230007)),
'BS': ('Bahamas', (-78.98, 23.71, -77.0, 27.04)),
'BA': ('Bosnia and Herz.', (15.7500260759, 42.65, 19.59976, 45.2337767604)),
'BY': ('Belarus', (23.1994938494, 51.3195034857, 32.6936430193, 56.1691299506)),
'BZ': ('Belize', (-89.2291216703, 15.8869375676, -88.1068129138, 18.4999822047)),
'BO': ('Bolivia', (-69.5904237535, -22.8729187965, -57.4983711412, -9.76198780685)),
'BR': ('Brazil', (-73.9872354804, -33.7683777809, -34.7299934555, 5.24448639569)),
'BN': ('Brunei', (114.204016555, 4.007636827, 115.450710484, 5.44772980389)),
'BT': ('Bhutan', (88.8142484883, 26.7194029811, 92.1037117859, 28.2964385035)),
'BW': ('Botswana', (19.8954577979, -26.8285429827, 29.4321883481, -17.6618156877)),
'CF': ('Central African Rep.', (14.4594071794, 2.2676396753, 27.3742261085, 11.1423951278)),
'CA': ('Canada', (-140.99778, 41.6751050889, -52.6480987209, 83.23324)),
'CH': ('Switzerland', (6.02260949059, 45.7769477403, 10.4427014502, 47.8308275417)),
'CL': ('Chile', (-75.6443953112, -55.61183, -66.95992, -17.5800118954)),
'CN': ('China', (73.6753792663, 18.197700914, 135.026311477, 53.4588044297)),
'CI': ('Ivory Coast', (-8.60288021487, 4.33828847902, -2.56218950033, 10.5240607772)),
'CM': ('Cameroon', (8.48881554529, 1.72767263428, 16.0128524106, 12.8593962671)),
'CD': ('Congo (Kinshasa)', (12.1823368669, -13.2572266578, 31.1741492042, 5.25608775474)),
'CG': ('Congo (Brazzaville)', (11.0937728207, -5.03798674888, 18.4530652198, 3.72819651938)),
'CO': ('Colombia', (-78.9909352282, -4.29818694419, -66.8763258531, 12.4373031682)),
'CR': ('Costa Rica', (-85.94172543, 8.22502798099, -82.5461962552, 11.2171192489)),
'CU': ('Cuba', (-84.9749110583, 19.8554808619, -74.1780248685, 23.1886107447)),
'CY': ('Cyprus', (32.2566671079, 34.5718694118, 34.0048808123, 35.1731247015)),
'CZ': ('Czech Rep.', (12.2401111182, 48.5553052842, 18.8531441586, 51.1172677679)),
'DE': ('Germany', (5.98865807458, 47.3024876979, 15.0169958839, 54.983104153)),
'DJ': ('Djibouti', (41.66176, 10.9268785669, 43.3178524107, 12.6996385767)),
'DK': ('Denmark', (8.08997684086, 54.8000145534, 12.6900061378, 57.730016588)),
'DO': ('Dominican Rep.', (-71.9451120673, 17.598564358, -68.3179432848, 19.8849105901)),
'DZ': ('Algeria', (-8.68439978681, 19.0573642034, 11.9995056495, 37.1183806422)),
'EC': ('Ecuador', (-80.9677654691, -4.95912851321, -75.2337227037, 1.3809237736)),
'EG': ('Egypt', (24.70007, 22.0, 36.86623, 31.58568)),
'ER': ('Eritrea', (36.3231889178, 12.4554157577, 43.0812260272, 17.9983074)),
'ES': ('Spain', (-9.39288367353, 35.946850084, 3.03948408368, 43.7483377142)),
'EE': ('Estonia', (23.3397953631, 57.4745283067, 28.1316992531, 59.6110903998)),
'ET': ('Ethiopia', (32.95418, 3.42206, 47.78942, 14.95943)),
'FI': ('Finland', (20.6455928891, 59.846373196, 31.5160921567, 70.1641930203)),
'FJ': ('Fiji', (-180.0, -18.28799, 180.0, -16.0208822567)),
'FK': ('Falkland Is.', (-61.2, -52.3, -57.75, -51.1)),
'FR': ('France', (-54.5247541978, 2.05338918702, 9.56001631027, 51.1485061713)),
'GA': ('Gabon', (8.79799563969, -3.97882659263, 14.4254557634, 2.32675751384)),
'GB': ('United Kingdom', (-7.57216793459, 49.959999905, 1.68153079591, 58.6350001085)),
'GE': ('Georgia', (39.9550085793, 41.0644446885, 46.6379081561, 43.553104153)),
'GH': ('Ghana', (-3.24437008301, 4.71046214438, 1.0601216976, 11.0983409693)),
'GN': ('Guinea', (-15.1303112452, 7.3090373804, -7.83210038902, 12.5861829696)),
'GM': ('Gambia', (-16.8415246241, 13.1302841252, -13.8449633448, 13.8764918075)),
'GW': ('Guinea Bissau', (-16.6774519516, 11.0404116887, -13.7004760401, 12.6281700708)),
'GQ': ('Eq. Guinea', (9.3056132341, 1.01011953369, 11.285078973, 2.28386607504)),
'GR': ('Greece', (20.1500159034, 34.9199876979, 26.6041955909, 41.8269046087)),
'GL': ('Greenland', (-73.297, 60.03676, -12.20855, 83.64513)),
'GT': ('Guatemala', (-92.2292486234, 13.7353376327, -88.2250227526, 17.8193260767)),
'GY': ('Guyana', (-61.4103029039, 1.26808828369, -56.5393857489, 8.36703481692)),
'HN': ('Honduras', (-89.3533259753, 12.9846857772, -83.147219001, 16.0054057886)),
'HR': ('Croatia', (13.6569755388, 42.47999136, 19.3904757016, 46.5037509222)),
'HT': ('Haiti', (-74.4580336168, 18.0309927434, -71.6248732164, 19.9156839055)),
'HU': ('Hungary', (16.2022982113, 45.7594811061, 22.710531447, 48.6238540716)),
'ID': ('Indonesia', (95.2930261576, -10.3599874813, 141.03385176, 5.47982086834)),
'IN': ('India', (68.1766451354, 7.96553477623, 97.4025614766, 35.4940095078)),
'IE': ('Ireland', (-9.97708574059, 51.6693012559, -6.03298539878, 55.1316222195)),
'IR': ('Iran', (44.1092252948, 25.0782370061, 63.3166317076, 39.7130026312)),
'IQ': ('Iraq', (38.7923405291, 29.0990251735, 48.5679712258, 37.3852635768)),
'IS': ('Iceland', (-24.3261840479, 63.4963829617, -13.609732225, 66.5267923041)),
'IL': ('Israel', (34.2654333839, 29.5013261988, 35.8363969256, 33.2774264593)),
'IT': ('Italy', (6.7499552751, 36.619987291, 18.4802470232, 47.1153931748)),
'JM': ('Jamaica', (-78.3377192858, 17.7011162379, -76.1996585761, 18.5242184514)),
'JO': ('Jordan', (34.9226025734, 29.1974946152, 39.1954683774, 33.3786864284)),
'JP': ('Japan', (129.408463169, 31.0295791692, 145.543137242, 45.5514834662)),
'KZ': ('Kazakhstan', (46.4664457538, 40.6623245306, 87.3599703308, 55.3852501491)),
'KE': ('Kenya', (33.8935689697, -4.67677, 41.8550830926, 5.506)),
'KG': ('Kyrgyzstan', (69.464886916, 39.2794632025, 80.2599902689, 43.2983393418)),
'KH': ('Cambodia', (102.3480994, 10.4865436874, 107.614547968, 14.5705838078)),
'KR': ('S. Korea', (126.117397903, 34.3900458847, 129.468304478, 38.6122429469)),
'KW': ('Kuwait', (46.5687134133, 28.5260627304, 48.4160941913, 30.0590699326)),
'LA': ('Laos', (100.115987583, 13.88109101, 107.564525181, 22.4647531194)),
'LB': ('Lebanon', (35.1260526873, 33.0890400254, 36.6117501157, 34.6449140488)),
'LR': ('Liberia', (-11.4387794662, 4.35575511313, -7.53971513511, 8.54105520267)),
'LY': ('Libya', (9.31941084152, 19.58047, 25.16482, 33.1369957545)),
'LK': ('Sri Lanka', (79.6951668639, 5.96836985923, 81.7879590189, 9.82407766361)),
'LS': ('Lesotho', (26.9992619158, -30.6451058896, 29.3251664568, -28.6475017229)),
'LT': ('Lithuania', (21.0558004086, 53.9057022162, 26.5882792498, 56.3725283881)),
'LU': ('Luxembourg', (5.67405195478, 49.4426671413, 6.24275109216, 50.1280516628)),
'LV': ('Latvia', (21.0558004086, 55.61510692, 28.1767094256, 57.9701569688)),
'MA': ('Morocco', (-17.0204284327, 21.4207341578, -1.12455115397, 35.7599881048)),
'MD': ('Moldova', (26.6193367856, 45.4882831895, 30.0246586443, 48.4671194525)),
'MG': ('Madagascar', (43.2541870461, -25.6014344215, 50.4765368996, -12.0405567359)),
'MX': ('Mexico', (-117.12776, 14.5388286402, -86.811982388, 32.72083)),
'MK': ('Macedonia', (20.46315, 40.8427269557, 22.9523771502, 42.3202595078)),
'ML': ('Mali', (-12.1707502914, 10.0963607854, 4.27020999514, 24.9745740829)),
'MM': ('Myanmar', (92.3032344909, 9.93295990645, 101.180005324, 28.335945136)),
'ME': ('Montenegro', (18.45, 41.87755, 20.3398, 43.52384)),
'MN': ('Mongolia', (87.7512642761, 41.5974095729, 119.772823928, 52.0473660345)),
'MZ': ('Mozambique', (30.1794812355, -26.7421916643, 40.7754752948, -10.3170960425)),
'MR': ('Mauritania', (-17.0634232243, 14.6168342147, -4.92333736817, 27.3957441269)),
'MW': ('Malawi', (32.6881653175, -16.8012997372, 35.7719047381, -9.23059905359)),
'MY': ('Malaysia', (100.085756871, 0.773131415201, 119.181903925, 6.92805288332)),
'NA': ('Namibia', (11.7341988461, -29.045461928, 25.0844433937, -16.9413428687)),
'NC': ('New Caledonia', (164.029605748, -22.3999760881, 167.120011428, -20.1056458473)),
'NE': ('Niger', (0.295646396495, 11.6601671412, 15.9032466977, 23.4716684026)),
'NG': ('Nigeria', (2.69170169436, 4.24059418377, 14.5771777686, 13.8659239771)),
'NI': ('Nicaragua', (-87.6684934151, 10.7268390975, -83.147219001, 15.0162671981)),
'NL': ('Netherlands', (3.31497114423, 50.803721015, 7.09205325687, 53.5104033474)),
'NO': ('Norway', (4.99207807783, 58.0788841824, 31.29341841, 80.6571442736)),
'NP': ('Nepal', (80.0884245137, 26.3978980576, 88.1748043151, 30.4227169866)),
'NZ': ('New Zealand', (166.509144322, -46.641235447, 178.517093541, -34.4506617165)),
'OM': ('Oman', (52.0000098, 16.6510511337, 59.8080603372, 26.3959343531)),
'PK': ('Pakistan', (60.8742484882, 23.6919650335, 77.8374507995, 37.1330309108)),
'PA': ('Panama', (-82.9657830472, 7.2205414901, -77.2425664944, 9.61161001224)),
'PE': ('Peru', (-81.4109425524, -18.3479753557, -68.6650797187, -0.0572054988649)),
'PH': ('Philippines', (117.17427453, 5.58100332277, 126.537423944, 18.5052273625)),
'PG': ('Papua New Guinea', (141.000210403, -10.6524760881, 156.019965448, -2.50000212973)),
'PL': ('Poland', (14.0745211117, 49.0273953314, 24.0299857927, 54.8515359564)),
'PR': ('Puerto Rico', (-67.2424275377, 17.946553453, -65.5910037909, 18.5206011011)),
'KP': ('N. Korea', (124.265624628, 37.669070543, 130.780007359, 42.9853868678)),
'PT': ('Portugal', (-9.52657060387, 36.838268541, -6.3890876937, 42.280468655)),
'PY': ('Paraguay', (-62.6850571357, -27.5484990374, -54.2929595608, -19.3427466773)),
'QA': ('Qatar', (50.7439107603, 24.5563308782, 51.6067004738, 26.1145820175)),
'RO': ('Romania', (20.2201924985, 43.6884447292, 29.62654341, 48.2208812526)),
'RU': ('Russia', (-180.0, 41.151416124, 180.0, 81.2504)),
'RW': ('Rwanda', (29.0249263852, -2.91785776125, 30.8161348813, -1.13465911215)),
'SA': ('Saudi Arabia', (34.6323360532, 16.3478913436, 55.6666593769, 32.161008816)),
'SD': ('Sudan', (21.93681, 8.61972971293, 38.4100899595, 22.0)),
'SS': ('S. Sudan', (23.8869795809, 3.50917, 35.2980071182, 12.2480077571)),
'SN': ('Senegal', (-17.6250426905, 12.332089952, -11.4678991358, 16.5982636581)),
'SB': ('Solomon Is.', (156.491357864, -10.8263672828, 162.398645868, -6.59933847415)),
'SL': ('Sierra Leone', (-13.2465502588, 6.78591685631, -10.2300935531, 10.0469839543)),
'SV': ('El Salvador', (-90.0955545723, 13.1490168319, -87.7235029772, 14.4241327987)),
'SO': ('Somalia', (40.98105, -1.68325, 51.13387, 12.02464)),
'RS': ('Serbia', (18.82982, 42.2452243971, 22.9860185076, 46.1717298447)),
'SR': ('Suriname', (-58.0446943834, 1.81766714112, -53.9580446031, 6.0252914494)),
'SK': ('Slovakia', (16.8799829444, 47.7584288601, 22.5581376482, 49.5715740017)),
'SI': ('Slovenia', (13.6981099789, 45.4523163926, 16.5648083839, 46.8523859727)),
'SE': ('Sweden', (11.0273686052, 55.3617373725, 23.9033785336, 69.1062472602)),
'SZ': ('Swaziland', (30.6766085141, -27.2858794085, 32.0716654803, -25.660190525)),
'SY': ('Syria', (35.7007979673, 32.312937527, 42.3495910988, 37.2298725449)),
'TD': ('Chad', (13.5403935076, 7.42192454674, 23.88689, 23.40972)),
'TG': ('Togo', (-0.0497847151599, 5.92883738853, 1.86524051271, 11.0186817489)),
'TH': ('Thailand', (97.3758964376, 5.69138418215, 105.589038527, 20.4178496363)),
'TJ': ('Tajikistan', (67.4422196796, 36.7381712916, 74.9800024759, 40.9602133245)),
'TM': ('Turkmenistan', (52.5024597512, 35.2706639674, 66.5461503437, 42.7515510117)),
'TL': ('East Timor', (124.968682489, -9.39317310958, 127.335928176, -8.27334482181)),
'TT': ('Trinidad and Tobago', (-61.95, 10.0, -60.895, 10.89)),
'TN': ('Tunisia', (7.52448164229, 30.3075560572, 11.4887874691, 37.3499944118)),
'TR': ('Turkey', (26.0433512713, 35.8215347357, 44.7939896991, 42.1414848903)),
'TW': ('Taiwan', (120.106188593, 21.9705713974, 121.951243931, 25.2954588893)),
'TZ': ('Tanzania', (29.3399975929, -11.7209380022, 40.31659, -0.95)),
'UG': ('Uganda', (29.5794661801, -1.44332244223, 35.03599, 4.24988494736)),
'UA': ('Ukraine', (22.0856083513, 44.3614785833, 40.0807890155, 52.3350745713)),
'UY': ('Uruguay', (-58.4270741441, -34.9526465797, -53.209588996, -30.1096863746)),
'US': ('United States', (-171.791110603, 18.91619, -66.96466, 71.3577635769)),
'UZ': ('Uzbekistan', (55.9289172707, 37.1449940049, 73.055417108, 45.5868043076)),
'VE': ('Venezuela', (-73.3049515449, 0.724452215982, -59.7582848782, 12.1623070337)),
'VN': ('Vietnam', (102.170435826, 8.59975962975, 109.33526981, 23.3520633001)),
'VU': ('Vanuatu', (166.629136998, -16.5978496233, 167.844876744, -14.6264970842)),
'PS': ('West Bank', (34.9274084816, 31.3534353704, 35.5456653175, 32.5325106878)),
'YE': ('Yemen', (42.6048726743, 12.5859504257, 53.1085726255, 19.0000033635)),
'ZA': ('South Africa', (16.3449768409, -34.8191663551, 32.830120477, -22.0913127581)),
'ZM': ('Zambia', (21.887842645, -17.9612289364, 33.4856876971, -8.23825652429)),
'ZW': ('Zimbabwe', (25.2642257016, -22.2716118303, 32.8498608742, -15.5077869605)),
}
def get_country_code(country_id):
if country_id in country.keys():
return country.get(country_id)
else:
return {}

View File

@ -0,0 +1,89 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.tools.safe_eval import safe_eval
import json
class KsDashboardNinjaTemplate(models.Model):
_name = 'ks_dashboard_ninja.board_defined_filters'
_description = 'Dashboard Ninja Defined Filters'
name = fields.Char('Filter Label')
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','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_domain = fields.Char(string="Domain", help="Define conditions for filter. ")
ks_domain_temp = fields.Char(string="Domain Substitute")
ks_model_name = fields.Char(related='ks_model_id.model', string="Model Name")
display_type = fields.Selection([
('line_section', "Section")], default=False, help="Technical field for UX purpose.")
sequence = fields.Integer(default=10,
help="Gives the sequence order when displaying a list of payment terms lines.")
ks_is_active = fields.Boolean(string="Active")
@api.onchange('ks_domain')
def ks_domain_onchange(self):
for rec in self:
if rec.ks_model_id:
try:
ks_domain = rec.ks_domain
if ks_domain and "%UID" in ks_domain:
ks_domain = ks_domain.replace('"%UID"', str(self.env.user.id))
if ks_domain and "%MYCOMPANY" in ks_domain:
ks_domain = ks_domain.replace('"%MYCOMPANY"', str(self.env.company.id))
self.env[rec.ks_model_id.model].search_count(safe_eval(ks_domain))
except Exception as e:
raise ValidationError(_("Something went wrong . Possibly it is due to wrong input type for domain"))
@api.constrains('ks_domain', 'ks_model_id')
def ks_domain_check(self):
for rec in self:
if rec.ks_model_id and not rec.ks_domain:
raise ValidationError(_("Domain can not be empty"))
class KsDashboardNinjaTemplate(models.Model):
_name = 'ks_dashboard_ninja.board_custom_filters'
_description = 'Dashboard Ninja Custom Filters'
name = fields.Char("Filter Label")
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','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_domain_field_id = fields.Many2one('ir.model.fields',
domain="[('model_id','=',ks_model_id),"
"('name','!=','id'),('store','=',True),"
"('ttype', 'in', ['boolean', 'char', "
"'date', 'datetime', 'float', 'integer', 'html', 'many2many', "
"'many2one', 'monetary', 'one2many', 'text', 'selection'])]",
string="Domain Field")
@api.onchange('ks_model_id')
def on_change_ks_model_id(self):
self.ks_domain_field_id = False
class KsDashboardNinjaTemplateFilters(models.Model):
_name = 'ks_dashboard_ninja.favourite_filters'
_description = 'Dashboard Ninja Favourite Filters'
name = fields.Char("Filter Label")
ks_dashboard_board_id = fields.Many2one('ks_dashboard_ninja.board', string="Dashboard")
ks_filter = fields.Char("Filter")
ks_access_id = fields.Integer("Access Id")
ks_filter_type = fields.Char(default='favourite')
_sql_constraints = [
('name_uniq', 'UNIQUE (name)', 'The name of the filter must be unique!'),
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
from odoo import models, fields, api, _
class KsDashboardNinjaTemplate(models.Model):
_name = 'ks_dashboard_ninja.board_template'
_description = 'Dashboard Ninja Template'
name = fields.Char()
ks_gridstack_config = fields.Char()
ks_item_count = fields.Integer()
ks_template_type = fields.Selection([('ks_default', 'Predefined'), ('ks_custom', 'Custom')],
string="Template Format")
ks_dashboard_item_ids = fields.One2many('ks_dashboard_ninja.item', 'ks_dashboard_board_template_id',
string="Template Type")
ks_dashboard_board_id = fields.Many2one('ks_dashboard_ninja.board', string="Dashboard", help="""
Items Configuration and their position in the dashboard will be copied from the selected dashboard
and will be saved as template.
""")
@api.model_create_multi
def create(self, vals_list):
for val in vals_list:
if val.get('ks_template_type', False) and val.get('ks_dashboard_board_id', False):
dashboard_id = self.env['ks_dashboard_ninja.board'].browse(val.get('ks_dashboard_board_id'))
val['ks_gridstack_config'] = dashboard_id.ks_gridstack_config
val['ks_item_count'] = len(dashboard_id.ks_dashboard_items_ids)
val['ks_dashboard_item_ids'] = [(4, x.copy({'ks_dashboard_ninja_board_id': False}).id) for x in
dashboard_id.ks_dashboard_items_ids]
recs = super(KsDashboardNinjaTemplate, self).create(vals_list)
return recs
def write(self, val):
if val.get('ks_dashboard_board_id', False):
dashboard_id = self.env['ks_dashboard_ninja.board'].browse(val.get('ks_dashboard_board_id'))
val['ks_gridstack_config'] = dashboard_id.ks_gridstack_config
val['ks_item_count'] = len(dashboard_id.ks_dashboard_items_ids)
val['ks_dashboard_item_ids'] = [(6, 0,
[x.copy({'ks_dashboard_ninja_board_id': False}).id for x in
dashboard_id.ks_dashboard_items_ids])]
recs = super(KsDashboardNinjaTemplate, self).write(val)
return recs

View File

@ -0,0 +1,141 @@
import json
from odoo import models, fields, api, _
import copy
import re
from odoo.exceptions import ValidationError, UserError
class KsDashboardNinjaItems(models.Model):
_inherit = 'ks_dashboard_ninja.item'
ks_to_do_preview = fields.Char("To Do Preview", default="To Do Preview")
ks_dn_header_lines = fields.One2many('ks_to.do.headers', 'ks_dn_item_id')
ks_to_do_data = fields.Char(string="To Do Data in JSon", compute='ks_get_to_do_view_data', compute_sudo=False)
ks_header_bg_color = fields.Char(string="Header Background Color", default="#8e24aa,0.99",
help=' Select the background color with transparency. ')
@api.depends('ks_dn_header_lines', 'ks_dashboard_item_type')
def ks_get_to_do_view_data(self):
for rec in self:
ks_to_do_data = rec._ksGetToDOData()
rec.ks_to_do_data = ks_to_do_data
def _ksGetToDOData(self):
ks_to_do_data = {
'label': [],
'ks_link': [],
'ks_href_id': [],
'ks_section_id': [],
'ks_content': {},
'ks_content_record_id': {},
'ks_content_active': {}
}
if self.ks_dn_header_lines:
for ks_dn_header_line in self.ks_dn_header_lines:
ks_to_do_header_label = ks_dn_header_line.ks_to_do_header[:]
ks_to_do_data['label'].append(ks_to_do_header_label)
ks_dn_header_line_id = str(ks_dn_header_line.id)
if type(ks_dn_header_line.id).__name__ != 'int' and ks_dn_header_line.id.ref != None:
ks_dn_header_line_id = ks_dn_header_line.id.ref
if ' ' in ks_dn_header_line.ks_to_do_header:
ks_temp = ks_dn_header_line.ks_to_do_header.replace(" ", "")
ks_to_do_data['ks_link'].append('#' + ks_temp + ks_dn_header_line_id)
ks_to_do_data['ks_href_id'].append(ks_temp + str(ks_dn_header_line.id))
elif ks_dn_header_line.ks_to_do_header[0].isdigit():
ks_temp = ks_dn_header_line.ks_to_do_header.replace(
ks_dn_header_line.ks_to_do_header[0], 'z')
ks_to_do_data['ks_link'].append('#' + ks_temp + ks_dn_header_line_id)
ks_to_do_data['ks_href_id'].append(ks_temp + str(ks_dn_header_line.id))
else:
ks_to_do_data['ks_link'].append('#' + ks_dn_header_line.ks_to_do_header + ks_dn_header_line_id)
ks_to_do_data['ks_href_id'].append(ks_dn_header_line.ks_to_do_header + str(ks_dn_header_line.id))
ks_to_do_data['ks_section_id'].append(str(ks_dn_header_line.id))
if len(ks_dn_header_line.ks_to_do_description_lines):
for ks_to_do_description_line in ks_dn_header_line.ks_to_do_description_lines:
if ' ' in ks_dn_header_line.ks_to_do_header or ks_dn_header_line.ks_to_do_header[0].isdigit():
if ks_to_do_data['ks_content'].get(ks_temp +
str(ks_dn_header_line.id), False):
ks_to_do_data['ks_content'][ks_temp +
str(ks_dn_header_line.id)].append(
ks_to_do_description_line.ks_description)
ks_to_do_data['ks_content_record_id'][ks_temp +
str(ks_dn_header_line.id)].append(
str(ks_to_do_description_line.id))
ks_to_do_data['ks_content_active'][ks_temp +
str(ks_dn_header_line.id)].append(
str(ks_to_do_description_line.ks_active))
else:
ks_to_do_data['ks_content'][ks_temp +
str(ks_dn_header_line.id)] = [
ks_to_do_description_line.ks_description]
ks_to_do_data['ks_content_record_id'][ks_temp +
str(ks_dn_header_line.id)] = [
str(ks_to_do_description_line.id)]
ks_to_do_data['ks_content_active'][ks_temp +
str(ks_dn_header_line.id)] = [
str(ks_to_do_description_line.ks_active)]
else:
if ks_to_do_data['ks_content'].get(ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id), False):
ks_to_do_data['ks_content'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)].append(
ks_to_do_description_line.ks_description)
ks_to_do_data['ks_content_record_id'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)].append(
str(ks_to_do_description_line.id))
ks_to_do_data['ks_content_active'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)].append(
str(ks_to_do_description_line.ks_active))
else:
ks_to_do_data['ks_content'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)] = [
ks_to_do_description_line.ks_description]
ks_to_do_data['ks_content_record_id'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)] = [
str(ks_to_do_description_line.id)]
ks_to_do_data['ks_content_active'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)] = [
str(ks_to_do_description_line.ks_active)]
ks_to_do_data = json.dumps(ks_to_do_data)
else:
ks_to_do_data = False
return ks_to_do_data
class KsToDoheaders(models.Model):
_name = 'ks_to.do.headers'
_description = "to do headers"
ks_dn_item_id = fields.Many2one('ks_dashboard_ninja.item')
ks_to_do_header = fields.Char('Header')
ks_to_do_description_lines = fields.One2many('ks_to.do.description', 'ks_to_do_header_id')
@api.constrains('ks_to_do_header')
def ks_to_do_header_check(self):
for rec in self:
if rec.ks_to_do_header:
ks_check = bool(re.match('^[A-Z, a-z,0-9,_]+$', rec.ks_to_do_header))
if not ks_check:
raise ValidationError(_("Special characters are not allowed only string and digits allow for section header"))
@api.onchange('ks_to_do_header')
def ks_to_do_header_onchange(self):
for rec in self:
if rec.ks_to_do_header:
ks_check = bool(re.match('^[A-Z, a-z,0-9,_]+$', rec.ks_to_do_header))
if not ks_check:
raise ValidationError(_("Special characters are not allowed only string and digits allow for section header"))
class KsToDODescription(models.Model):
_name = 'ks_to.do.description'
_description = 'to do description'
ks_to_do_header_id = fields.Many2one('ks_to.do.headers')
ks_description = fields.Text('Description')
ks_active = fields.Boolean('Active Description', default=True)

View File

@ -0,0 +1,30 @@
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", domain="[('parent_id','=',False)]",
required=True,
default=lambda self: self.env['ir.ui.menu'].search(
[('name', '=', 'My Dashboards')]))
def ks_do_action(self):
for rec in self:
try:
ks_result = base64.b64decode(rec.ks_import_dashboard)
self.env['ks_dashboard_ninja.board'].ks_import_dashboard(ks_result, self.ks_top_menu_id)
except Exception as e:
_logger.warning("Error importing dashboard for record %s: %s", rec.id, str(e))
raise ValidationError(_("%s") % str(e))
return {
'type': 'ir.actions.client',
'tag': 'reload',
}

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
class KsDashboardNinjaBoardItemAction(models.TransientModel):
_name = 'ks_ninja_dashboard.item_action'
_description = 'Dashboard Ninja Item Actions'
name = fields.Char()
ks_dashboard_item_ids = fields.Many2many("ks_dashboard_ninja.item", string="Dashboard Items")
ks_action = fields.Selection([('move', 'Move'),
('duplicate', 'Duplicate'),
], string="Action")
ks_dashboard_ninja_id = fields.Many2one("ks_dashboard_ninja.board", string="Select Dashboard")
ks_dashboard_ninja_ids = fields.Many2many("ks_dashboard_ninja.board", string="Select Dashboards")
# Move or Copy item to another dashboard action
def action_item_move_copy_action(self):
if self.ks_action == 'move':
for item in self.ks_dashboard_item_ids:
item.ks_dashboard_ninja_board_id = self.ks_dashboard_ninja_id
elif self.ks_action == 'duplicate':
# Using sudo here to allow creating same item without any security error
for dashboard_id in self.ks_dashboard_ninja_ids:
for item in self.ks_dashboard_item_ids:
item.sudo().copy({'ks_dashboard_ninja_board_id': dashboard_id.id})

View File

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

View File

@ -0,0 +1,52 @@
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')
enable_chart_zoom = fields.Boolean(string="Enable Zooming for charts", store=True,
config_parameter='ks_dashboard_ninja.enable_chart_zoom')
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")
ks_analysis_word_length = fields.Selection([("50","50 words"),("100","100 words"),("150","150 words"),("200","200 words"),],default ="100", string="AI Analysis length", store=True,config_parameter="ks_dashboard_ninja.ks_analysis_word_length")
def Open_wizard(self):
if self.url and self.ks_email_id:
try:
url = self.url + "/api/v1/ks_dn_fetch_api"
json_data = {'email':self.ks_email_id,
'url':self.env['ir.config_parameter'].sudo().get_param('web.base.url'),
'db_name':self.env.cr.dbname
}
ks_ai_response = requests.post(url,data=json_data)
except Exception as e:
raise ValidationError(_("Please enter correct URL"))
if ks_ai_response.status_code == 200:
try:
ks_ai_response = json.loads(ks_ai_response.text)
except Exception as e:
ks_ai_response = False
if ks_ai_response == "success":
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Success'),
'message': 'API key sent on Email ID',
'sticky': False,
}
}
elif ks_ai_response == 'key already generated':
raise ValidationError(
_("key already generated.If you need assistance, feel free to contact at sales@ksolves.com"))
else:
raise ValidationError(_("Either you have entered wrong URL path or there is some problem in sending request. If you need assistance, feel free to contact at sales@ksolves.com"))
else:
raise ValidationError(_("Some problem in sending request.Please contact at sales@ksolves.com"))
else:
raise ValidationError(_("Please enter URL and Email ID"))

View File

@ -0,0 +1,5 @@
xlrd==2.0.1
openpyxl == 3.1.2
gTTS == 2.5.1
pandas==2.1.2
SQLAlchemy==2.0.32

View File

@ -0,0 +1,36 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ks_dashboard_ninja_board,ks_dashboard_ninja.board,model_ks_dashboard_ninja_board,base.group_user,1,1,1,1
access_ks_dashboard_ninja_kpi_mail,ks_dashboard_ninja.kpi_mail,model_ks_dashboard_ninja_kpi_mail,base.group_user,1,1,1,1
access_ks_dashboard_ninja_item,ks_dashboard_ninja.item,model_ks_dashboard_ninja_item,base.group_user,1,1,1,1
access_ks_to_do_headers,ks_to.do.headers,model_ks_to_do_headers,base.group_user,1,1,1,1
access_ks_to_do_description,ks_to.do.description,model_ks_to_do_description,base.group_user,1,1,1,1
access_ks_dashboard_ninja_child_board,ks_dashboard_ninja.child_board,model_ks_dashboard_ninja_child_board,base.group_user,1,1,1,1
access_ks_dashboard_ninja_board_defined_filters,ks_dashboard_ninja.board_defined_filters,model_ks_dashboard_ninja_board_defined_filters,base.group_user,1,1,1,1
access_ks_dashboard_ninja_board_custom_filters,ks_dashboard_ninja.board_custom_filters,model_ks_dashboard_ninja_board_custom_filters,base.group_user,1,1,1,1
access_ks_dashboard_ninja_board_template,ks_dashboard_ninja.board_template,model_ks_dashboard_ninja_board_template,base.group_user,1,1,1,1
access_ks_dashboard_ninja_item_goal,ks_dashboard_ninja_item_goal,model_ks_dashboard_ninja_item_goal,base.group_user,1,1,1,1
access_ks_dashboard_ninja_item_action,ks_dashboard_ninja_item_action,model_ks_dashboard_ninja_item_action,base.group_user,1,1,1,1
access_ks_dashboard_item_multiplier,ks_dashboard_item.multiplier,model_ks_dashboard_item_multiplier,base.group_user,1,1,1,1
access_ks_ninja_dashboard_item_action,ks_ninja_dashboard.item_action,model_ks_ninja_dashboard_item_action,base.group_user,1,1,1,0
access_ks_dashboard_group_by,ks.dashboard.group.by,model_ks_dashboard_group_by,base.group_user,1,1,1,1
access_ks_dashboard_csv_group_by,ks.dashboard.csv.group.by,model_ks_dashboard_csv_group_by,base.group_user,1,1,1,1
access_ks_dashboard_new,ks.dashboard.new,model_ks_dashboard_new,base.group_user,1,1,1,1
access_ks_dashboard_csv_new,ks.dashboard.csv.new,model_ks_dashboard_csv_new,base.group_user,1,1,1,1
access_ks_dashboard_ninja_import,ks_dashboard_ninja.import,model_ks_dashboard_ninja_import,base.group_system,1,1,1,0
access_ir_actions_act_window_view,ir.actions.act_window.view,base.model_ir_actions_act_window_view,base.group_user,1,0,0,0
access_ir_actions_act_window,ir.actions.act_window,base.model_ir_actions_act_window,base.group_user,1,0,0,0
access_ir_actions_client,ir.actions.client,base.model_ir_actions_client,base.group_user,1,0,0,0
access_ir_ui_menu,ir.ui.menu,base.model_ir_ui_menu,base.group_user,1,1,0,0
access_ir_model_group_user,ir.model,base.model_ir_model,base.group_user,1,0,0,0
access_ir_model_fields_group_user,ir.model.fields,base.model_ir_model_fields,base.group_user,1,0,0,0
access_ir_model_ks_dashboard_wizard,ks_dashboard_wizard,model_ks_dashboard_wizard,base.group_user,1,1,1,1
access_ir_model_ks_duplicate_dashboard_wizard,ks_duplicate_dashboard__wizard,model_ks_dashboard_duplicate_wizard,base.group_user,1,1,1,1
access_ir_model_ks_delete_dashboard_wizard,ks_delete_dashboard__wizard,model_ks_dashboard_delete_wizard,base.group_user,1,1,1,1
access_ks_dashboard_ninja_arti_int,ks_dashboard_ninja.arti_int,model_ks_dashboard_ninja_arti_int,base.group_user,1,1,1,1
access_ks_dashboard_ninja_ai_dashboard,ks_dashboard_ninja.ai_dashboard,model_ks_dashboard_ninja_ai_dashboard,base.group_user,1,1,1,1
access_ks_dashboard_ninja_fetch_key,ks_dashboard_ninja.fetch_key,model_ks_dashboard_ninja_fetch_key,base.group_user,1,1,1,1
access_ks_dashboard_ninja_favourite_filters,ks_dashboard_ninja.favourite_filters,model_ks_dashboard_ninja_favourite_filters,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_ks_dashboard_ninja_board ks_dashboard_ninja.board model_ks_dashboard_ninja_board base.group_user 1 1 1 1
3 access_ks_dashboard_ninja_kpi_mail ks_dashboard_ninja.kpi_mail model_ks_dashboard_ninja_kpi_mail base.group_user 1 1 1 1
4 access_ks_dashboard_ninja_item ks_dashboard_ninja.item model_ks_dashboard_ninja_item base.group_user 1 1 1 1
5 access_ks_to_do_headers ks_to.do.headers model_ks_to_do_headers base.group_user 1 1 1 1
6 access_ks_to_do_description ks_to.do.description model_ks_to_do_description base.group_user 1 1 1 1
7 access_ks_dashboard_ninja_child_board ks_dashboard_ninja.child_board model_ks_dashboard_ninja_child_board base.group_user 1 1 1 1
8 access_ks_dashboard_ninja_board_defined_filters ks_dashboard_ninja.board_defined_filters model_ks_dashboard_ninja_board_defined_filters base.group_user 1 1 1 1
9 access_ks_dashboard_ninja_board_custom_filters ks_dashboard_ninja.board_custom_filters model_ks_dashboard_ninja_board_custom_filters base.group_user 1 1 1 1
10 access_ks_dashboard_ninja_board_template ks_dashboard_ninja.board_template model_ks_dashboard_ninja_board_template base.group_user 1 1 1 1
11 access_ks_dashboard_ninja_item_goal ks_dashboard_ninja_item_goal model_ks_dashboard_ninja_item_goal base.group_user 1 1 1 1
12 access_ks_dashboard_ninja_item_action ks_dashboard_ninja_item_action model_ks_dashboard_ninja_item_action base.group_user 1 1 1 1
13 access_ks_dashboard_item_multiplier ks_dashboard_item.multiplier model_ks_dashboard_item_multiplier base.group_user 1 1 1 1
14 access_ks_ninja_dashboard_item_action ks_ninja_dashboard.item_action model_ks_ninja_dashboard_item_action base.group_user 1 1 1 0
15 access_ks_dashboard_group_by ks.dashboard.group.by model_ks_dashboard_group_by base.group_user 1 1 1 1
16 access_ks_dashboard_csv_group_by ks.dashboard.csv.group.by model_ks_dashboard_csv_group_by base.group_user 1 1 1 1
17 access_ks_dashboard_new ks.dashboard.new model_ks_dashboard_new base.group_user 1 1 1 1
18 access_ks_dashboard_csv_new ks.dashboard.csv.new model_ks_dashboard_csv_new base.group_user 1 1 1 1
19 access_ks_dashboard_ninja_import ks_dashboard_ninja.import model_ks_dashboard_ninja_import base.group_system 1 1 1 0
20 access_ir_actions_act_window_view ir.actions.act_window.view base.model_ir_actions_act_window_view base.group_user 1 0 0 0
21 access_ir_actions_act_window ir.actions.act_window base.model_ir_actions_act_window base.group_user 1 0 0 0
22 access_ir_actions_client ir.actions.client base.model_ir_actions_client base.group_user 1 0 0 0
23 access_ir_ui_menu ir.ui.menu base.model_ir_ui_menu base.group_user 1 1 0 0
24 access_ir_model_group_user ir.model base.model_ir_model base.group_user 1 0 0 0
25 access_ir_model_fields_group_user ir.model.fields base.model_ir_model_fields base.group_user 1 0 0 0
26 access_ir_model_ks_dashboard_wizard ks_dashboard_wizard model_ks_dashboard_wizard base.group_user 1 1 1 1
27 access_ir_model_ks_duplicate_dashboard_wizard ks_duplicate_dashboard__wizard model_ks_dashboard_duplicate_wizard base.group_user 1 1 1 1
28 access_ir_model_ks_delete_dashboard_wizard ks_delete_dashboard__wizard model_ks_dashboard_delete_wizard base.group_user 1 1 1 1
29 access_ks_dashboard_ninja_arti_int ks_dashboard_ninja.arti_int model_ks_dashboard_ninja_arti_int base.group_user 1 1 1 1
30 access_ks_dashboard_ninja_ai_dashboard ks_dashboard_ninja.ai_dashboard model_ks_dashboard_ninja_ai_dashboard base.group_user 1 1 1 1
31 access_ks_dashboard_ninja_fetch_key ks_dashboard_ninja.fetch_key model_ks_dashboard_ninja_fetch_key base.group_user 1 1 1 1
32 access_ks_dashboard_ninja_favourite_filters ks_dashboard_ninja.favourite_filters model_ks_dashboard_ninja_favourite_filters 1 1 1 1

View File

@ -0,0 +1,59 @@
<odoo>
<data noupdate="1">
<record id="ir_rule_ks_dashboard_item_company_restrictions" model="ir.rule">
<field name="name">Dashboard Item Company Restriction: User Can only view their company and sub companies
items.
</field>
<field name="model_id" ref="model_ks_dashboard_ninja_item"/>
<field name="domain_force">
['|',('ks_company_id','in', company_ids),('ks_company_id','=',False)]</field>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
</record>
<record id="ir_rule_ks_accessible_dashboards" model="ir.rule">
<field name="name">Dashboard Record Level Groups Access: Show dashboards matching user's assigned groups.</field>
<field name="model_id" ref="model_ks_dashboard_ninja_board"/>
<field name="domain_force">['|', ('ks_dashboard_group_access', '=' , False), ('ks_dashboard_group_access','in', user.groups_id.ids)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]" />
</record>
<record id="ir_rule_ks_accessible_child_dashboards" model="ir.rule">
<field name="name">Child Dashboard Record Level Groups Access: Show dashboards matching user's assigned groups.</field>
<field name="model_id" ref="model_ks_dashboard_ninja_child_board"/>
<field name="domain_force">['|', ('ks_computed_group_access', '=', False), ('ks_computed_group_access', 'in', user.groups_id.ids)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]" />
</record>
<record id="ir_rule_ks_admin_accessible_dashboards" model="ir.rule">
<field name="name">Dashboard Record Level Groups Access: Show all dashboards to admin regardless of assigned groups.</field>
<field name="model_id" ref="model_ks_dashboard_ninja_board"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[Command.link(ref('base.group_system'))]"/>
</record>
<record id="ir_rule_ks_admin_accessible_child_dashboards" model="ir.rule">
<field name="name">Child Dashboard Record Level Groups Access: Show all dashboards to admin regardless of assigned groups.</field>
<field name="model_id" ref="model_ks_dashboard_ninja_child_board"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[Command.link(ref('base.group_system'))]"/>
</record>
<record model="ir.module.category" id="ks_dashboard_ninja_security_groups">
<field name="name">Dashboard Ninja Rights</field>
</record>
<record model="res.groups" id="ks_dashboard_ninja_group_manager">
<field name="name">Show Full Dashboard Features</field>
<field name="category_id" ref="ks_dashboard_ninja.ks_dashboard_ninja_security_groups"/>
</record>
<record id="base.group_system" model="res.groups">
<field name="implied_ids" eval="[(4, ref('ks_dashboard_ninja.ks_dashboard_ninja_group_manager'))]"/>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,5 @@
<svg width="55" height="55" viewBox="0 0 55 55" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.6084 5.1793L44.5271 13.223C46.2688 14.1626 46.2688 16.8439 44.5271 17.7835L29.6084 25.8272C28.2792 26.5376 26.7209 26.5376 25.3917 25.8272L10.4729 17.7835C8.73127 16.8439 8.73127 14.1626 10.4729 13.223L25.3917 5.1793C26.7209 4.46888 28.2792 4.46888 29.6084 5.1793Z" stroke="#241C1D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.2729 23.2143L22.1375 30.158C23.8562 31.0288 24.9562 32.7934 24.9562 34.7184V47.8268C24.9562 49.7289 22.9625 50.9434 21.2666 50.0955L7.40206 43.1518C5.68331 42.2809 4.58331 40.5163 4.58331 38.5913V25.483C4.58331 23.5809 6.57706 22.3663 8.2729 23.2143Z" stroke="#241C1D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M46.7271 23.2143L32.8625 30.158C31.1438 31.0288 30.0438 32.7934 30.0438 34.7184V47.8268C30.0438 49.7289 32.0375 50.9434 33.7333 50.0955L47.5979 43.1518C49.3167 42.2809 50.4167 40.5163 50.4167 38.5913V25.483C50.4167 23.5809 48.4229 22.3663 46.7271 23.2143Z" stroke="#241C1D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 19.9997C3.33398 12.1429 3.33398 8.21456 5.77476 5.77378C8.21554 3.33301 12.1439 3.33301 20.0007 3.33301C27.8574 3.33301 31.7858 3.33301 34.2265 5.77378C36.6673 8.21456 36.6673 12.1429 36.6673 19.9997C36.6673 27.8564 36.6673 31.7848 34.2265 34.2256C31.7858 36.6663 27.8574 36.6663 20.0007 36.6663C12.1439 36.6663 8.21554 36.6663 5.77476 34.2256C3.33398 31.7848 3.33398 27.8564 3.33398 19.9997Z" stroke="#1C1C1C" stroke-width="2"/>
<path d="M11.666 23.3337L14.6608 19.7399C15.8476 18.3158 16.4409 17.6037 17.2216 17.6037C18.0022 17.6037 18.5956 18.3158 19.7823 19.7399L20.2164 20.2608C21.4031 21.6849 21.9965 22.3969 22.7771 22.3969C23.5578 22.3969 24.1511 21.6849 25.3379 20.2608L28.3327 16.667" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 875 B

View File

@ -0,0 +1,6 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 19.9997C3.33398 12.1429 3.33398 8.21456 5.77476 5.77378C8.21554 3.33301 12.1439 3.33301 20.0007 3.33301C27.8574 3.33301 31.7858 3.33301 34.2265 5.77378C36.6673 8.21456 36.6673 12.1429 36.6673 19.9997C36.6673 27.8564 36.6673 31.7848 34.2265 34.2256C31.7858 36.6663 27.8574 36.6663 20.0007 36.6663C12.1439 36.6663 8.21554 36.6663 5.77476 34.2256C3.33398 31.7848 3.33398 27.8564 3.33398 19.9997Z" stroke="#1C1C1C" stroke-width="2"/>
<path d="M11.666 30V15" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M20 30V10" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M28.334 30.0003V21.667" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 805 B

View File

@ -0,0 +1,6 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.334 21.667C14.2545 21.667 15.0007 20.9208 15.0007 20.0003C15.0007 19.0799 14.2545 18.3337 13.334 18.3337C12.4135 18.3337 11.6673 19.0799 11.6673 20.0003C11.6673 20.9208 12.4135 21.667 13.334 21.667Z" fill="#1C1C1C"/>
<path d="M21.6673 20.0003C21.6673 20.9208 20.9211 21.667 20.0007 21.667C19.0802 21.667 18.334 20.9208 18.334 20.0003C18.334 19.0799 19.0802 18.3337 20.0007 18.3337C20.9211 18.3337 21.6673 19.0799 21.6673 20.0003Z" fill="#1C1C1C"/>
<path d="M26.6673 21.667C27.5878 21.667 28.334 20.9208 28.334 20.0003C28.334 19.0799 27.5878 18.3337 26.6673 18.3337C25.7468 18.3337 25.0007 19.0799 25.0007 20.0003C25.0007 20.9208 25.7468 21.667 26.6673 21.667Z" fill="#1C1C1C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5733 5.41699C13.5104 5.41697 11.0843 5.41695 9.18561 5.67222C7.23158 5.93493 5.65 6.48846 4.40272 7.73573C3.15545 8.983 2.60192 10.5646 2.33921 12.5186C2.08394 14.4173 2.08396 16.8434 2.08398 19.9063V20.0944C2.08396 23.1573 2.08394 25.5833 2.33921 27.482C2.60192 29.4361 3.15545 31.0177 4.40272 32.2649C5.65 33.5122 7.23158 34.0657 9.18561 34.3284C11.0843 34.5837 13.5103 34.5837 16.5732 34.5837H23.428C26.4909 34.5837 28.917 34.5837 30.8157 34.3284C32.7697 34.0657 34.3513 33.5122 35.5986 32.2649C36.8459 31.0177 37.3994 29.4361 37.6621 27.482C37.9174 25.5834 37.9173 23.1573 37.9173 20.0944V19.9063C37.9173 16.8434 37.9174 14.4173 37.6621 12.5186C37.3994 10.5646 36.8459 8.983 35.5986 7.73573C34.3513 6.48846 32.7697 5.93493 30.8157 5.67222C28.917 5.41695 26.491 5.41697 23.428 5.41699H16.5733ZM6.17049 9.5035C6.87584 8.79815 7.84192 8.37537 9.51873 8.14993C11.2315 7.91965 13.4893 7.91699 16.6673 7.91699H23.334C26.512 7.91699 28.7698 7.91965 30.4826 8.14993C32.1594 8.37537 33.1255 8.79815 33.8308 9.5035C34.5362 10.2088 34.9589 11.1749 35.1844 12.8517C35.4147 14.5645 35.4173 16.8223 35.4173 20.0003C35.4173 23.1784 35.4147 25.4361 35.1844 27.1489C34.9589 28.8257 34.5362 29.7918 33.8308 30.4972C33.1255 31.2025 32.1594 31.6253 30.4826 31.8507C28.7698 32.081 26.512 32.0837 23.334 32.0837H16.6673C13.4893 32.0837 11.2315 32.081 9.51873 31.8507C7.84192 31.6253 6.87584 31.2025 6.17049 30.4972C5.46514 29.7918 5.04236 28.8257 4.81692 27.1489C4.58664 25.4361 4.58399 23.1784 4.58399 20.0003C4.58399 16.8223 4.58664 14.5645 4.81692 12.8517C5.04236 11.1749 5.46514 10.2088 6.17049 9.5035Z" fill="#1C1C1C"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,5 @@
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.01721 5.0351C8.24008 4.4328 9.09196 4.4328 9.31483 5.0351L10.7978 9.04284C10.8679 9.2322 11.0172 9.3815 11.2066 9.45157L15.2143 10.9346C15.8166 11.1574 15.8166 12.0093 15.2143 12.2322L11.2066 13.7152C11.0172 13.7853 10.8679 13.9346 10.7978 14.1239L9.31483 18.1316C9.09196 18.7339 8.24008 18.7339 8.01721 18.1316L6.53421 14.1239C6.46414 13.9346 6.31484 13.7853 6.12548 13.7152L2.11774 12.2322C1.51544 12.0093 1.51544 11.1574 2.11774 10.9346L6.12548 9.45157C6.31484 9.3815 6.46414 9.2322 6.53421 9.04284L8.01721 5.0351Z" fill="#ABC8E7" stroke="#6789C6" stroke-width="1.25"/>
<path d="M17.623 5L13.623 5" stroke="#6789C6" stroke-width="1.25" stroke-linecap="round"/>
<path d="M15.623 7L15.623 3" stroke="#6789C6" stroke-width="1.25" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0007 2.08301C10.1055 2.08301 2.08398 10.1046 2.08398 19.9997C2.08398 29.8948 10.1055 37.9163 20.0007 37.9163C29.8958 37.9163 37.9173 29.8948 37.9173 19.9997C37.9173 10.1046 29.8958 2.08301 20.0007 2.08301ZM4.58398 19.9997C4.58398 16.1925 5.96405 12.7076 8.25121 10.018L13.5887 15.3554C12.6421 16.66 12.084 18.2647 12.084 19.9997C12.084 21.7347 12.6421 23.3394 13.5886 24.6439L8.2512 29.9813C5.96405 27.2918 4.58398 23.8069 4.58398 19.9997ZM15.3564 13.5877L10.019 8.25023C12.7086 5.96307 16.1934 4.58301 20.0007 4.58301C23.8079 4.58301 27.2927 5.96307 29.9823 8.25023L24.6449 13.5877C23.3403 12.6411 21.7357 12.083 20.0007 12.083C18.2656 12.083 16.661 12.6411 15.3564 13.5877ZM10.019 31.7491C12.7086 34.0363 16.1934 35.4163 20.0007 35.4163C23.8079 35.4163 27.2927 34.0363 29.9823 31.7491L24.6449 26.4117C23.3403 27.3582 21.7357 27.9163 20.0007 27.9163C18.2656 27.9163 16.661 27.3582 15.3564 26.4117L10.019 31.7491ZM26.4127 24.6439L31.7501 29.9813C34.0373 27.2918 35.4173 23.8069 35.4173 19.9997C35.4173 16.1925 34.0373 12.7076 31.7501 10.018L26.4126 15.3554C27.3592 16.66 27.9173 18.2647 27.9173 19.9997C27.9173 21.7347 27.3592 23.3394 26.4127 24.6439ZM14.584 19.9997C14.584 17.0081 17.0091 14.583 20.0007 14.583C22.9922 14.583 25.4173 17.0081 25.4173 19.9997C25.4173 22.9912 22.9922 25.4163 20.0007 25.4163C17.0091 25.4163 14.584 22.9912 14.584 19.9997Z" fill="#1C1C1C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,6 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.76044 33.0423L6.761 33.0428C6.82645 33.1093 6.89234 33.1752 6.95866 33.2405L6.95913 33.241C10.8038 37.0329 17.4193 34.2692 17.4193 28.7803V22.582H11.2211C5.73227 22.582 2.96851 29.1976 6.76044 33.0423ZM17.4193 20.582H11.2211C4.00159 20.582 0.266883 29.3066 5.33649 34.4467C5.40873 34.52 5.48147 34.5927 5.55472 34.6649C10.6948 39.7345 19.4193 35.9998 19.4193 28.7803V22.582V20.582H17.4193Z" fill="#1C1C1C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.4221 6.41028C30.3447 3.37612 25.0176 5.58641 25.0176 10.0103V14.9824H29.9898C34.415 14.9824 36.6243 9.65289 33.5898 6.57783M25.0176 16.9824H29.9898C36.1381 16.9824 39.3319 9.54851 35.0129 5.17258C34.9512 5.11006 34.889 5.04795 34.8265 4.98625C30.4488 0.669851 23.0176 3.86251 23.0176 10.0103V14.9824V16.9824H25.0176ZM33.4222 6.41039C33.4783 6.46572 33.5341 6.52143 33.5895 6.5775L33.4222 6.41039Z" fill="#1C1C1C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.0176 22.4424V20.4424H25.0176H28.2789C32.9246 20.4424 35.3475 26.0624 32.0827 29.3675C32.0357 29.415 31.9885 29.4623 31.9409 29.5092C28.634 32.7704 23.0176 30.3481 23.0176 25.7036V22.4424ZM30.6598 27.962C30.6188 28.0035 30.5775 28.0448 30.536 28.0858L30.6598 27.962ZM30.536 28.0858C28.5346 30.0585 25.0176 28.6316 25.0176 25.7036V22.4424H28.2789C31.2089 22.4424 32.6341 25.9626 30.6603 27.9615" fill="#1C1C1C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.1211 15.6758L21.1211 17.6758L19.1211 17.6758L14.9191 17.6758C9.44285 17.6758 6.58673 11.051 10.4352 7.15495C10.4906 7.09892 10.5463 7.04325 10.6023 6.98795C14.5006 3.14365 21.1211 5.99903 21.1211 11.474L21.1211 15.6758ZM11.8576 8.56092C9.30016 11.1508 11.1585 15.6758 14.9191 15.6758L19.1211 15.6758L19.1211 11.474C19.1211 7.7152 14.5992 5.85532 12.0067 8.41198C11.9569 8.46113 11.9074 8.51062 11.8581 8.56045" fill="#1C1C1C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.1515 36.6663C20.7543 36.6663 21.3324 36.4269 21.7586 36.0007C22.1848 35.5745 22.4242 34.9964 22.4242 34.3936V25.583L34.1894 11.7876C34.5283 11.4854 34.8018 11.1172 34.9933 10.7055C35.1848 10.2939 35.2902 9.84745 35.303 9.39361C35.303 5.45422 27.5 3.33301 20.1515 3.33301C12.803 3.33301 5 5.45422 5 9.39361C5.01451 9.84897 5.12137 10.2966 5.31411 10.7095C5.50684 11.1223 5.78143 11.4916 6.12121 11.7951L17.8788 25.583V34.3936C17.8788 34.9964 18.1182 35.5745 18.5445 36.0007C18.9707 36.4269 19.5487 36.6663 20.1515 36.6663ZM20.303 4.99967C28.3409 4.99967 33.3333 7.2421 33.3333 9.39361C33.3096 9.77448 33.1604 10.1368 32.9091 10.4239L32.697 10.6739C31.0152 12.4239 26.5985 13.333 20.1515 13.333C13.7045 13.333 9.18182 12.4239 7.5 10.6815L7.25758 10.4012C7.01389 10.1196 6.87007 9.76538 6.84848 9.39361C6.84848 7.2421 12.2652 4.99967 20.303 4.99967ZM9.93939 13.9391C13.2406 14.9867 16.6884 15.4982 20.1515 15.4542C23.6147 15.4982 27.0624 14.9867 30.3636 13.9391L21.0909 24.8103C20.9736 24.9475 20.9091 25.1221 20.9091 25.3027V34.3936C20.9091 34.5945 20.8293 34.7872 20.6872 34.9293C20.5451 35.0714 20.3524 35.1512 20.1515 35.1512C19.9506 35.1512 19.7579 35.0714 19.6158 34.9293C19.4738 34.7872 19.3939 34.5945 19.3939 34.3936V25.3027C19.394 25.1221 19.3295 24.9475 19.2121 24.8103L9.93939 13.9391Z" fill="#1C1C1C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,6 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 19.9997C3.33398 12.1429 3.33398 8.21456 5.77476 5.77378C8.21554 3.33301 12.1439 3.33301 20.0007 3.33301C27.8574 3.33301 31.7858 3.33301 34.2265 5.77378C36.6673 8.21456 36.6673 12.1429 36.6673 19.9997C36.6673 27.8564 36.6673 31.7848 34.2265 34.2256C31.7858 36.6663 27.8574 36.6663 20.0007 36.6663C12.1439 36.6663 8.21554 36.6663 5.77476 34.2256C3.33398 31.7848 3.33398 27.8564 3.33398 19.9997Z" stroke="#1C1C1C" stroke-width="2"/>
<path d="M12.5 11.667L17.5 11.667" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M12.5 20H22.5" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M12.5 28.334H27.5" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 815 B

View File

@ -0,0 +1,6 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4H36.3333" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M14.666 18.1673L16.8208 16.0125C17.3764 15.4569 17.6542 15.1792 17.9993 15.1792C18.3445 15.1792 18.6223 15.4569 19.1779 16.0125L20.1542 16.9888C20.7097 17.5444 20.9875 17.8221 21.3327 17.8221C21.6779 17.8221 21.9556 17.5444 22.5112 16.9888L24.666 14.834" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M19.666 35.667L19.666 29.0003" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M33.0007 4V18.1667C33.0007 23.2735 33.0007 25.827 31.327 27.4135C29.6533 29 26.9596 29 21.5721 29H17.7626C12.3751 29 9.68134 29 8.00766 27.4135C6.33398 25.827 6.33398 23.2735 6.33398 18.1667V4" stroke="#1C1C1C" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 846 B

View File

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M36.6673 36.6663H20.0007C12.1439 36.6663 8.21554 36.6663 5.77476 34.2256C3.33398 31.7848 3.33398 27.8564 3.33398 19.9997V3.33301" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M31.6669 11.667L26.4702 18.211C25.6742 19.2134 25.2762 19.7146 24.8193 19.9589C24.1167 20.3344 23.2772 20.3527 22.559 20.008C22.0919 19.7839 21.6725 19.3005 20.8336 18.3336C19.9947 17.3668 19.5753 16.8834 19.1082 16.6592C18.39 16.3146 17.5505 16.3328 16.8479 16.7084C16.391 16.9527 15.993 17.4538 15.197 18.4562L10 25.0003" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 693 B

View File

@ -0,0 +1,8 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.33398 19.9997C4.33398 16.043 4.33611 13.1762 4.63016 10.989C4.92035 8.8306 5.47825 7.48451 6.48187 6.48089C7.48549 5.47727 8.83157 4.91938 10.99 4.62919C13.1771 4.33513 16.044 4.33301 20.0007 4.33301C23.9573 4.33301 26.8242 4.33513 29.0113 4.62919C31.1697 4.91938 32.5158 5.47727 33.5194 6.48089C34.5231 7.48451 35.0809 8.8306 35.3711 10.989C35.6652 13.1762 35.6673 16.043 35.6673 19.9997C35.6673 23.9563 35.6652 26.8232 35.3711 29.0104C35.0809 31.1688 34.5231 32.5148 33.5194 33.5185C32.5158 34.5221 31.1697 35.08 29.0113 35.3702C26.8242 35.6642 23.9573 35.6663 20.0007 35.6663C16.044 35.6663 13.1771 35.6642 10.99 35.3702C8.83157 35.08 7.48549 34.5221 6.48187 33.5185C5.47825 32.5148 4.92035 31.1688 4.63016 29.0104C4.33611 26.8232 4.33398 23.9563 4.33398 19.9997Z" stroke="#1C1C1C" stroke-width="2"/>
<path d="M5 16.667H35" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M5 26.667H35" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M11.666 16.667L11.666 35.0003" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M20 16.667L20 35.0003" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M28.334 16.667L28.334 35.0003" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.0007 36.6663C12.1439 36.6663 8.21554 36.6663 5.77476 34.2256C3.33398 31.7848 3.33398 27.8564 3.33398 19.9997C3.33398 12.1429 3.33398 8.21456 5.77476 5.77379C8.21554 3.33301 12.1439 3.33301 20.0007 3.33301C27.8574 3.33301 31.7858 3.33301 34.2265 5.77378C36.6673 8.21456 36.6673 12.1429 36.6673 19.9997C36.6673 27.8564 36.6673 31.7848 34.2265 34.2256C31.7858 36.6663 27.8574 36.6663 20.0007 36.6663Z" stroke="#1C1C1C" stroke-width="2"/>
<path d="M5.83398 34.1663L20.0007 19.9997M34.1673 5.83301L20.0007 19.9997M34.1673 34.1663L20.0007 19.9997" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 717 B

View File

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M33.334 25.921C31.3778 32.1492 25.5592 36.667 18.6854 36.667C10.207 36.667 3.33398 29.7939 3.33398 21.3156C3.33398 14.4418 7.85174 8.62316 14.08 6.66699" stroke="#1C1C1C" stroke-width="2" stroke-linecap="round"/>
<path d="M36.5225 16.5785C34.7532 10.2403 29.76 5.2471 23.4219 3.47784C20.6825 2.71318 18.334 5.08999 18.334 7.93404V19.0915C18.334 20.5136 19.4868 21.6663 20.9088 21.6663H32.0663C34.9103 21.6663 37.2871 19.3178 36.5225 16.5785Z" stroke="#1C1C1C" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 591 B

View File

@ -0,0 +1,3 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M33.0757 21.0763C32.8834 17.0117 30.9667 13.2617 27.9417 11.0324L29.7405 7.50666L29.2262 7.16991C26.9709 5.69294 24.3989 4.91236 21.7864 4.91236C19.3739 4.91236 16.9989 5.58652 14.8721 6.85202L13.0762 3.33301L12.5608 3.6711C6.8691 7.39688 3.33398 14.3245 3.33398 21.7498V22.4233H14.0739C14.2554 25.0519 15.4899 27.4684 17.4179 28.9649L15.6209 32.4866L16.1352 32.8234C17.856 33.9501 19.8108 34.5462 21.7864 34.5462C23.5637 34.5462 25.3233 34.0599 26.9126 33.1413L28.7114 36.6663L29.2268 36.3282C33.8167 33.3251 36.6673 27.7385 36.6673 21.7498V21.0763H33.0757ZM31.8876 21.0763H22.8173L27.3477 12.1969C30.0078 14.1844 31.6983 17.4906 31.8876 21.0763ZM15.4637 8.01178C17.4096 6.8628 19.5828 6.25935 21.7864 6.25935C23.9899 6.25935 26.1637 6.86347 28.109 8.01178L21.7864 20.4028L17.8739 12.735L15.4637 8.01178ZM12.6471 5.18445L20.7554 21.0763H14.0483H4.53517C4.73458 14.61 7.78994 8.62399 12.6471 5.18445ZM15.2661 22.4233H20.7554L18.7203 26.4117L18.0137 27.7964C16.4507 26.545 15.4423 24.5724 15.2661 22.4233ZM26.3185 31.9775C23.503 33.5797 20.0703 33.579 17.2542 31.9781L18.4346 29.6647L19.2251 28.1163L21.787 23.0967L25.4453 30.2654L26.3185 31.9775ZM29.1399 34.8129L22.8179 22.4233H33.0959H35.4643C35.2679 27.4516 32.8965 32.0987 29.1399 34.8129Z" fill="#1C1C1C" stroke="#1C1C1C" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

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