879 lines
38 KiB
Python
Executable File
879 lines
38 KiB
Python
Executable File
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||
# this repository contains the full copyright notices and license terms.
|
||
from trytond.model import ModelSQL, ModelView, fields, sequence_ordered, ModelSingleton
|
||
from trytond.pyson import Eval
|
||
from trytond.transaction import Transaction
|
||
from trytond.pool import Pool, PoolMeta
|
||
from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction
|
||
from decimal import getcontext, Decimal, ROUND_HALF_UP
|
||
import jwt
|
||
import datetime
|
||
import logging
|
||
from trytond.exceptions import UserWarning, UserError
|
||
import json
|
||
import shlex
|
||
from datetime import date
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
keywords = ["duplicate", "create", "add", "invoice", "reception", "pay", "delivery", "match"]
|
||
|
||
class DashboardLoader(Wizard):
|
||
'Load Dashboard'
|
||
__name__ = 'purchase.dashboard.loader'
|
||
|
||
start = StateAction('purchase_trade.act_dashboard_form')
|
||
|
||
def do_start(self, action):
|
||
pool = Pool()
|
||
Dashboard = pool.get('purchase.dashboard')
|
||
user_id = Transaction().user
|
||
if user_id == 0:
|
||
user_id = Transaction().context.get('user', user_id)
|
||
dashboard = Dashboard.search([('user_id', '=', user_id)])
|
||
if not dashboard:
|
||
dashboard, = Dashboard.create([{'user_id': user_id}])
|
||
else:
|
||
dashboard = dashboard[0]
|
||
action['views'].reverse()
|
||
return action, {'res_id': [dashboard.id]}
|
||
|
||
class DashboardContext(ModelView):
|
||
"Dashboard context"
|
||
__name__ = 'dashboard.context'
|
||
user = fields.Many2One('res.user', 'User')
|
||
company = fields.Many2One('company.company', 'Company', required=True)
|
||
|
||
@staticmethod
|
||
def default_company():
|
||
return Transaction().context.get('company')
|
||
|
||
@staticmethod
|
||
def default_user():
|
||
user_id = Transaction().user
|
||
if user_id == 0:
|
||
user_id = Transaction().context.get('user', user_id)
|
||
return user_id
|
||
|
||
class Demos(ModelSQL, ModelView):
|
||
'Demos'
|
||
__name__ = 'demos.demos'
|
||
|
||
title = fields.Char('Title', required=True)
|
||
category = fields.Char('Category')
|
||
icon = fields.Char('Icon') # exemple: "🚢" ou "📝"
|
||
active = fields.Boolean('Active')
|
||
video_file = fields.Char('File')
|
||
description = fields.Text('Description')
|
||
|
||
class News(ModelSQL, ModelView):
|
||
'News'
|
||
__name__ = 'news.news'
|
||
|
||
title = fields.Char('Title', required=True)
|
||
category = fields.Char('Category')
|
||
icon = fields.Char('Icon') # exemple: "🚢" ou "📝"
|
||
active = fields.Boolean('Active')
|
||
publish_date = fields.Date('Publish Date')
|
||
model = fields.Char('Model') # ex: 'purchase.purchase'
|
||
model_id = fields.Integer('Record ID')
|
||
|
||
class Dashboard(ModelSQL, ModelView):
|
||
"Dashbot"
|
||
__name__ = "purchase.dashboard"
|
||
|
||
stock = fields.One2Many('lot.lot','dashboard',"STOCK")
|
||
stock2 = fields.One2Many('lot.lot','dashboard',"PRODUCT")
|
||
stock3 = fields.One2Many('lot.lot','dashboard',"CURVE")
|
||
transit = fields.One2Many('stock.shipment.in','dashboard',"VESSEL IN TRANSIT")
|
||
document = fields.One2Many('document.incoming','dashboard',"Documents received")
|
||
html_content = fields.Function(fields.Text('HTML Content'),'get_news')
|
||
demos = fields.Function(fields.Text('Demos'),'get_demos')
|
||
#metabase = fields.Function(fields.Text("",states={'invisible': ~Eval('metabase',False)}),'gen_url')
|
||
metabase = fields.Text("",states={'invisible': ~Eval('metabase',False)})
|
||
chatbot = fields.Text("")
|
||
input = fields.Text("")
|
||
bi_id = fields.Integer("")
|
||
user = fields.Function(fields.Char(""), 'get_user')
|
||
fake = fields.Char("",readonly=True)
|
||
res_id = fields.Integer("Res id")
|
||
action_return = fields.Char("Action return")
|
||
actions = fields.One2Many('bot.action','dashboard',"Actions")
|
||
user_request = fields.Char("User request")
|
||
user_id = fields.Many2One('res.user',"User")
|
||
tremor = fields.Function(fields.Text(""),'get_tremor')
|
||
|
||
@classmethod
|
||
def default_bi_id(cls):
|
||
return 38
|
||
|
||
@classmethod
|
||
def default_res_id(cls):
|
||
return 0
|
||
|
||
@fields.depends('input')
|
||
def on_change_input(self):
|
||
logger.info("ENTERONCHANGE",self.input)
|
||
Lot = Pool().get('lot.lot')
|
||
self.metabase = None
|
||
if self.input is None:
|
||
self.chatbot = 'chatbot:' + json.dumps([{"type": "bot", "content": "🧠 Hi Admin, how can I help you?"}], ensure_ascii=False)
|
||
else:
|
||
dial = json.loads(self.input)
|
||
last_content = dial[-1]["content"]
|
||
last_type = dial[-1]["type"]
|
||
logger.info("ON_CHANGE_INPUT:%s",last_content)
|
||
message = ''
|
||
if "display" in last_content.lower() and "clients" in last_content.lower():
|
||
dial.append({"type": "bot", "content": "Clients displayed below"})
|
||
if self.bi_id != 7:
|
||
self.bi_id = 7
|
||
self.metabase = self.gen_url()
|
||
elif "display" in last_content.lower() and "sales" in last_content.lower():
|
||
dial.append({"type": "bot", "content": "Sales displayed below"})
|
||
if self.bi_id != 41:
|
||
self.bi_id = 41
|
||
self.metabase = self.gen_url()
|
||
elif "display" in last_content.lower() and "products" in last_content.lower():
|
||
dial.append({"type": "bot", "content": "Products displayed below"})
|
||
if self.bi_id != 38:
|
||
self.bi_id = 38
|
||
self.metabase = self.gen_url()
|
||
elif "open" in last_content.lower() and "shipment" in last_content.lower():
|
||
sh = self.FindInContent('shipment',last_content)
|
||
if sh:
|
||
dial.append({"type": "bot", "content": "Ok shipment opened in new tab"})
|
||
dial.append({"type": "do", "content": "open,stock.shipment.in," + str(sh.id) + ",0"})
|
||
else:
|
||
dial.append({"type": "bot", "content": "No shipment found"})
|
||
elif "where" in last_content.lower() and "vessel" in last_content.lower():
|
||
sh = self.FindInContent('shipment',last_content)
|
||
if sh:
|
||
dial.append({"type": "bot", "content": "Look at the map below"})
|
||
self.metabase = f"imo:{sh.vessel.vessel_imo}"
|
||
elif "graph" in last_content.lower() and "lot" in last_content.lower():
|
||
g,l,i = last_content.split()
|
||
lot = Lot(i)
|
||
if lot:
|
||
dial.append({"type": "bot", "content": "Graph displayed below"})
|
||
self.metabase = lot.get_flow_graph()
|
||
elif "pivot" in last_content.lower() and "lot" in last_content.lower():
|
||
g,l,i = last_content.split()
|
||
lot = Lot(i)
|
||
if lot:
|
||
dial.append({"type": "bot", "content": "Pivot displayed below"})
|
||
self.metabase = lot.get_pivot()
|
||
elif "display" in last_content.lower() and "map" in last_content.lower():
|
||
dial.append({"type": "bot", "content": "Map displayed below"})
|
||
self.metabase = self.get_map()
|
||
elif any(k in last_content.lower() for k in keywords):
|
||
logger.info("ACTION_RETURN:%s",self.action_return)
|
||
if "help" in last_content.lower():
|
||
if "duplicate" in last_content.lower():
|
||
dial.append({"type": "bot", "content": "duplicate [purchase/sale]* [party]* [qt]* [plan] [period] [price]"})
|
||
elif self.action_return:
|
||
model,res_id, res_nb = self.action_return.split(",")
|
||
z,keyword = model.split(".")
|
||
message = {
|
||
"type": "bot",
|
||
"content": (
|
||
f'Action done. '
|
||
f'<a href="#" class="tryton-link" data-model="{model}" '
|
||
f'data-id="{res_id}" data-view="form">Open {keyword} {res_nb}</a>'
|
||
)
|
||
}
|
||
dial.append(message)
|
||
else:
|
||
if "match" in last_content.lower():
|
||
dial.append({"type": "bot", "content": "Matching done"})
|
||
else:
|
||
dial.append({"type": "bot", "content": "Not done"})
|
||
else:
|
||
dial.append({"type": "bot", "content": "Ok done"})
|
||
self.chatbot = 'chatbot:' + json.dumps(dial, ensure_ascii=False)
|
||
logger.info("EXITONCHANGE",self.chatbot)
|
||
|
||
def get_last_five_fx_rates(self, from_code='USD', to_code='EUR'):
|
||
"""
|
||
Retourne (dernier_taux, avant_dernier_taux) pour le couple de devises.
|
||
"""
|
||
Currency = Pool().get('currency.currency')
|
||
CurrencyRate = Pool().get('currency.currency.rate')
|
||
|
||
# Récupérer les devises EUR et USD
|
||
from_currency = Currency.search([('name', '=', from_code)])[0]
|
||
to_currency = Currency.search([('name', '=', to_code)])[0]
|
||
|
||
# Recherche des taux de la devise de base (ex: USD)
|
||
rates = CurrencyRate.search(
|
||
[('currency', '=', to_currency.id)],
|
||
order=[('date', 'DESC')],
|
||
limit=5,
|
||
)
|
||
|
||
if not rates:
|
||
return None, None, None, None, None
|
||
|
||
# Calcul du taux EUR/USD
|
||
# Si la devise principale de la société est EUR, et que le taux stocké est
|
||
# "1 USD = X EUR", on veut l'inverse pour avoir EUR/USD
|
||
f1 = rates[0].rate
|
||
f2 = rates[1].rate if len(rates) > 1 else None
|
||
f3 = rates[2].rate if len(rates) > 2 else None
|
||
f4 = rates[3].rate if len(rates) > 3 else None
|
||
f5 = rates[4].rate if len(rates) > 4 else None
|
||
d1 = rates[0].date
|
||
d2 = rates[1].date if len(rates) > 1 else None
|
||
d3 = rates[2].date if len(rates) > 2 else None
|
||
d4 = rates[3].date if len(rates) > 3 else None
|
||
d5 = rates[4].date if len(rates) > 4 else None
|
||
|
||
# if from_currency != to_currency:
|
||
# last_rate = 1 / last_rate if last_rate else None
|
||
# prev_rate = 1 / prev_rate if prev_rate else None
|
||
|
||
return round(1/f1,6), round(1/f2,6) if f2 else None, round(1/f3,6) if f3 else None, round(1/f4,6) if f4 else None, round(1/f5,6) if f5 else None, d1, d2, d3, d4, d5
|
||
|
||
def get_tremor(self,name):
|
||
Date = Pool().get('ir.date')
|
||
Configuration = Pool().get('gr.configuration')
|
||
config = Configuration.search(['id','>',0])[0]
|
||
Shipment = Pool().get('stock.shipment.in')
|
||
DocumentIncoming = Pool().get('document.incoming')
|
||
Fee = Pool().get('fee.fee')
|
||
WR = Pool().get('weight.report')
|
||
if config.automation:
|
||
shipment = Shipment.search([('state','!=','received')])
|
||
shipment_trend = [sh for sh in shipment if sh.create_date == Date.today()]
|
||
controller = Shipment.search([('controller','!=',None)])
|
||
controller_trend = [co for co in controller if co.create_date == Date.today()]
|
||
instruction = Shipment.search([('result','!=',None)])
|
||
instruction_trend = [si for si in instruction if si.create_date == Date.today()]
|
||
id_received = Shipment.search([('returned_id','!=',None)])
|
||
id_received_trend = [i for i in id_received if i.create_date == Date.today()]
|
||
wr = WR.search([('id','>',0)])
|
||
wr_trend = [w for w in wr if w.create_date == Date.today()]
|
||
so = Fee.search(['id','=',25])
|
||
so_trend = [s for s in so if s.create_date == Date.today()]
|
||
di = DocumentIncoming.search(['id','>',0])
|
||
di_trend = [d for d in di if d.create_date == Date.today()]
|
||
return (
|
||
config.dashboard +
|
||
"/dashboard/index.html?shipment="
|
||
+ str(len(shipment))
|
||
+ "&shipment_trend="
|
||
+ str(len(shipment_trend))
|
||
+ "&controller="
|
||
+ str(len(controller))
|
||
+ "&controller_trend="
|
||
+ str(len(controller_trend))
|
||
+ "&instruction="
|
||
+ str(len(instruction))
|
||
+ "&instruction_trend="
|
||
+ str(len(instruction_trend))
|
||
+ "&wr="
|
||
+ str(len(wr))
|
||
+ "&wr_trend="
|
||
+ str(len(wr_trend))
|
||
+ "&so="
|
||
+ str(len(so))
|
||
+ "&so_trend="
|
||
+ str(len(so_trend))
|
||
+ "&di="
|
||
+ str(len(di))
|
||
+ "&di_trend="
|
||
+ str(len(di_trend))
|
||
+ "&id_received="
|
||
+ str(len(id_received))
|
||
+ "&id_received_trend="
|
||
+ str(len(id_received_trend)))
|
||
|
||
f1,f2,f3,f4,f5,d1,d2,d3,d4,d5 = self.get_last_five_fx_rates()
|
||
Valuation = Pool().get('valuation.valuation')
|
||
last_total,last_variation = Valuation.get_totals()
|
||
pnl_amount = "{:,.0f}".format(round(last_total,0))
|
||
pnl_variation = 0
|
||
if last_total and last_variation:
|
||
pnl_variation = "{:,.2f}".format(round((last_variation/last_total)*100,0))
|
||
Open = Pool().get('open.position')
|
||
opens = Open.search(['id','>',0])
|
||
exposure = "{:,.0f}".format(round(sum([e.net_exposure for e in opens]),0))
|
||
ToPay = Pool().get('account.invoice')
|
||
topays = ToPay.search(['type','=','in'])
|
||
amounts = ToPay.get_amount_to_pay(topays)
|
||
total = sum(amounts.values(), Decimal(0))
|
||
topay = "{:,.0f}".format(round(total,0))
|
||
ToReceive = Pool().get('account.invoice')
|
||
toreceives = ToReceive.search(['type','=','out'])
|
||
amounts = ToPay.get_amount_to_pay(toreceives)
|
||
total = sum(amounts.values(), Decimal(0))
|
||
toreceive = "{:,.0f}".format(round(total,0))
|
||
Purchase = Pool().get('purchase.purchase')
|
||
draft = Purchase.search(['state','=','draft'])
|
||
draft_p = len(draft)
|
||
val = Purchase.search(['state','=','quotation'])
|
||
val_p = len(val)
|
||
conf = Purchase.search(['state','=','confirmed'])
|
||
conf_p = len(conf)
|
||
Sale = Pool().get('sale.sale')
|
||
draft = Sale.search(['state','=','draft'])
|
||
draft_s = len(draft)
|
||
val = Sale.search(['state','=','quotation'])
|
||
val_s = len(val)
|
||
conf = Sale.search(['state','=','confirmed'])
|
||
conf_s = len(conf)
|
||
|
||
draft = Shipment.search(['state','=','draft'])
|
||
shipment_d = len(draft)
|
||
val = Shipment.search(['state','=','started'])
|
||
shipment_s = len(val)
|
||
conf = Shipment.search(['state','=','received'])
|
||
shipment_r = len(conf)
|
||
Lot = Pool().get('lot.lot')
|
||
lots = Lot.search([('sale_line','!=',None),('line','!=',None),('lot_type','=','physic')])
|
||
lot_m = len(lots)
|
||
val = Lot.search([('sale_line','=',None),('line','!=',None),('lot_type','=','physic')])
|
||
lot_a = len(val)
|
||
Invoice = Pool().get('account.invoice')
|
||
invs = Invoice.search(['type','=','in'])
|
||
inv_p = len(invs)
|
||
invs = Invoice.search([('type','=','in'),('state','=','paid')])
|
||
inv_p_p = len(invs)
|
||
invs = Invoice.search([('type','=','in'),('state','!=','paid')])
|
||
inv_p_np = len(invs)
|
||
invs = Invoice.search(['type','=','out'])
|
||
inv_s = len(invs)
|
||
invs = Invoice.search([('type','=','out'),('state','=','paid')])
|
||
inv_s_p = len(invs)
|
||
invs = Invoice.search([('type','=','out'),('state','!=','paid')])
|
||
inv_s_np = len(invs)
|
||
AccountMove = Pool().get('account.move')
|
||
accs = AccountMove.search([('journal','=',3),('state','!=','posted')])
|
||
pay_val = len(accs)
|
||
accs = AccountMove.search([('journal','=',3),('state','=','posted')])
|
||
pay_posted = len(accs)
|
||
|
||
return (
|
||
config.dashboard +
|
||
"/dashboard/index.html?pnl_amount="
|
||
+ str(pnl_amount)
|
||
+ "&pnl_variation="
|
||
+ str(pnl_variation)
|
||
+ "&exposure="
|
||
+ str(exposure)
|
||
+ "&topay="
|
||
+ str(topay)
|
||
+ "&toreceive="
|
||
+ str(toreceive)
|
||
+ "&eurusd="
|
||
+ str(f1)
|
||
+ "&eurusd="
|
||
+ str(f2)
|
||
+ "&eurusd="
|
||
+ str(f3)
|
||
+ "&eurusd="
|
||
+ str(f4)
|
||
+ "&eurusd="
|
||
+ str(f5)
|
||
+ "&eurusd_date="
|
||
+ str(d1)
|
||
+ "&eurusd_date="
|
||
+ str(d2)
|
||
+ "&eurusd_date="
|
||
+ str(d3)
|
||
+ "&eurusd_date="
|
||
+ str(d4)
|
||
+ "&eurusd_date="
|
||
+ str(d5)
|
||
+ "&draft_p="
|
||
+ str(draft_p)
|
||
+ "&val_p="
|
||
+ str(val_p)
|
||
+ "&conf_p="
|
||
+ str(conf_p)
|
||
+ "&draft_s="
|
||
+ str(draft_s)
|
||
+ "&val_s="
|
||
+ str(val_s)
|
||
+ "&conf_s="
|
||
+ str(conf_s)
|
||
+ "&shipment_d="
|
||
+ str(shipment_d)
|
||
+ "&shipment_s="
|
||
+ str(shipment_s)
|
||
+ "&shipment_r="
|
||
+ str(shipment_r)
|
||
+ "&lot_m="
|
||
+ str(lot_m)
|
||
+ "&lot_a="
|
||
+ str(lot_a)
|
||
+ "&inv_p="
|
||
+ str(inv_p)
|
||
+ "&inv_p_p="
|
||
+ str(inv_p_p)
|
||
+ "&inv_p_np="
|
||
+ str(inv_p_np)
|
||
+ "&inv_s="
|
||
+ str(inv_s)
|
||
+ "&inv_s_p="
|
||
+ str(inv_s_p)
|
||
+ "&inv_s_np="
|
||
+ str(inv_s_np)
|
||
+ "&pay_val="
|
||
+ str(pay_val)
|
||
+ "&pay_posted="
|
||
+ str(pay_posted)
|
||
)
|
||
|
||
|
||
def get_news(self, name):
|
||
News = Pool().get('news.news')
|
||
Date = Pool().get('ir.date')
|
||
news_list = News.search([('active', '=', True)], limit=5, order=[('publish_date', 'DESC')])
|
||
last_rate,prev_rate, = self.get_last_five_fx_rates()
|
||
if last_rate and prev_rate:
|
||
variation = ((last_rate - prev_rate) / prev_rate) * 100 if prev_rate else 0
|
||
direction = "📈" if variation > 0 else "📉"
|
||
|
||
# 🆕 Création d’une “fake news” locale
|
||
forex_news = News()
|
||
forex_news.icon = direction
|
||
forex_news.category = "Forex"
|
||
# Détermine la direction et le style couleur
|
||
if variation > 0:
|
||
arrow = "⬆️"
|
||
color = "#28a745" # vert
|
||
elif variation < 0:
|
||
arrow = "⬇️"
|
||
color = "#dc3545" # rouge
|
||
else:
|
||
arrow = "➡️"
|
||
color = "#6c757d" # gris neutre
|
||
|
||
# Contenu HTML enrichi
|
||
forex_news.title = (
|
||
f"EUR/USD: {last_rate:.4f}"
|
||
f"<span style='color:{color}; font-weight:bold;'>"
|
||
f" {variation:+.2f}%"
|
||
f"</span> "
|
||
)
|
||
|
||
forex_news.publish_date = Date.today()
|
||
forex_news.model = 'currency.currency' # par exemple 'forex.forex'
|
||
forex_news.model_id = 2
|
||
forex_news.active = True
|
||
|
||
# On insère la “news” au début de la liste
|
||
news_list.insert(0, forex_news)
|
||
html_parts = [
|
||
'<div class="last-news">',
|
||
' <div class="last-news-title">Last news</div>'
|
||
]
|
||
|
||
for n in news_list:
|
||
icon = n.icon or "📰"
|
||
category = n.category or "General"
|
||
title = n.title or ""
|
||
# content = (n.content or "").replace('\n', '<br/>') <div class="last-news-content">{content}</div>
|
||
publish_date = n.publish_date.strftime('%d-%m-%Y') if n.publish_date else ""
|
||
|
||
# Génération du lien Tryton
|
||
if n.model and n.model_id:
|
||
# 🆕 lien JS pour ouvrir un tab interne Tryton
|
||
js_link = (
|
||
f"javascript:Sao.Tab.create({{"
|
||
f"model: '{n.model}', "
|
||
f"res_id: {n.model_id}, "
|
||
f"mode: ['form'], "
|
||
f"target: 'new'"
|
||
f"}});"
|
||
)
|
||
title_html = (
|
||
f'<a href="{js_link}" style="text-decoration:none; color:#2a5db0;">'
|
||
f'{title}</a>'
|
||
)
|
||
else:
|
||
title_html = f'<span>{title}</span>'
|
||
|
||
html_parts.append(f'''
|
||
<div class="last-news-item" style="margin-bottom: 8px;">
|
||
<div class="last-news-category">{icon} {category}</div>
|
||
<div class="last-news-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||
<div class="last-news-title-item"><strong>{title_html}</strong></div>
|
||
<div class="last-news-date" style="color:#666; font-size:0.9em;">{publish_date}</div>
|
||
</div>
|
||
</div>
|
||
''')
|
||
|
||
html_parts.append('</div>')
|
||
return "\n".join(html_parts)
|
||
|
||
def get_demos(self, name):
|
||
Demos = Pool().get('demos.demos')
|
||
html_parts = [
|
||
'<div class="demos" style="background-color:#f5f5f5; padding:12px; border-radius:12px;>',
|
||
' <div class="demos-title" style="font-size:1.2em; font-weight:bold; margin-bottom:10px;">🎬 Available Demo</div>'
|
||
]
|
||
|
||
demos = Demos.search([('active', '=', True)],order=[('id', 'DESC')])
|
||
for n in demos:
|
||
icon = n.icon or "📰"
|
||
category = n.category or "General"
|
||
title = n.title or "Sans titre"
|
||
description = n.description or ""
|
||
# suppose que n.video_path contient un chemin du type '/videos/demo1.mp4'
|
||
video_url = f"/videos/{n.video_file}" if getattr(n, 'video_file', None) else None
|
||
|
||
# lien vers le lecteur vidéo du navigateur
|
||
video_link = (
|
||
f'<a href="{video_url}" target="_blank" '
|
||
'style="color:#007bff; text-decoration:none;">Play the video 🎥</a>'
|
||
if video_url else
|
||
'<span style="color:gray;">No available video</span>'
|
||
)
|
||
|
||
html_parts.append(f'''
|
||
<div class="demo-item" style="margin-bottom: 12px; border-bottom:1px solid #eee; padding-bottom:8px;">
|
||
<div class="demo-category" style="font-size:0.9em; color:#666;">{icon} {category}</div>
|
||
<div class="demo-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||
<div class="demo-title" style="font-weight:bold;">{title}</div>
|
||
<div class="demo-link">{video_link}</div>
|
||
</div>
|
||
<div class="demo-description" style="font-size:0.85em; color:#555;">{description}</div>
|
||
</div>
|
||
''')
|
||
|
||
html_parts.append('</div>')
|
||
return "\n".join(html_parts)
|
||
|
||
def get_map(self):
|
||
departure = { "name":"SANTOS","lat": str(-23.9), "lon": str(-46.3) }
|
||
arrival = { "name":"ISTANBUL","lat": str(41), "lon": str(29) }
|
||
data = {
|
||
"highlightedCountryNames": [{"name":"Turkey"},{"name":"Brazil"}],
|
||
"routes": [[
|
||
{ "lon": -46.3, "lat": -23.9 },
|
||
{ "lon": -30.0, "lat": -20.0 },
|
||
{ "lon": -30.0, "lat": 0.0 },
|
||
{ "lon": -6.0, "lat": 35.9 },
|
||
{ "lon": 15.0, "lat": 38.0 },
|
||
{ "lon": 29.0, "lat": 41.0 }
|
||
]],
|
||
"boats": [{
|
||
"name": "CARIBBEAN 1",
|
||
"imo": "1234567",
|
||
"lon": -30.0,
|
||
"lat": 0.0,
|
||
"status": "En route",
|
||
"links": [
|
||
{ "text": "Voir sur VesselFinder", "url": "https://www.vesselfinder.com" },
|
||
{ "text": "Détails techniques", "url": "https://example.com/tech" }
|
||
],
|
||
"actions": [
|
||
{ "type": "track", "id": "123", "label": "Suivre ce bateau" },
|
||
{ "type": "details", "id": "123", "label": "Voir détails" }
|
||
]
|
||
}],
|
||
"cottonStocks": [
|
||
{ "name":"Mali","lat": 12.65, "lon": -8.0, "amount": 300 },
|
||
{ "name":"Egypte","lat": 30.05, "lon": 31.25, "amount": 500 },
|
||
{ "name":"Irak","lat": 33.0, "lon": 44.0, "amount": 150 }
|
||
],
|
||
"departures": [departure],
|
||
"arrivals": [arrival]
|
||
}
|
||
|
||
return "d3:" + json.dumps(data)
|
||
|
||
def FindInContent(self,what,content):
|
||
if what == 'shipment':
|
||
Shipment = Pool().get('stock.shipment.in')
|
||
shipments = Shipment.search([('id','>',0),('vessel','!=',None)],order=[('create_date', 'DESC')])
|
||
if shipments:
|
||
for sh in shipments:
|
||
if sh.vessel.vessel_name.lower() in content.lower():
|
||
return sh
|
||
elif what == 'purchase':
|
||
Purchase = Pool().get('purchase.purchase')
|
||
purchases = Purchase.search([('id','>',0)],order=[('create_date', 'DESC')])
|
||
if purchases:
|
||
for pu in purchases:
|
||
if pu.party.name.lower() in content.lower():
|
||
return pu
|
||
|
||
def gen_url(self,name=None):
|
||
Configuration = Pool().get('gr.configuration')
|
||
config = Configuration.search(['id','>',0])[0]
|
||
payload = {
|
||
"resource": {"dashboard": self.bi_id},
|
||
"params": {},
|
||
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
|
||
}
|
||
token = jwt.encode(payload, config.payload, algorithm="HS256")
|
||
logger.info("TOKEN:%s",token)
|
||
return f"metabase:http://vps107.geneva.hosting:3000/embed/dashboard/{token}#bordered=true&titled=true"
|
||
|
||
# @fields.depends('input')
|
||
# def on_change_with_metabase(self):
|
||
# return self.gen_url()
|
||
|
||
# @classmethod
|
||
# def default_html_content(cls):
|
||
# return 'http://62.72.36.116:3000/question/41-sales-by-clients-and-suppliers'
|
||
|
||
def get_user(self,name):
|
||
User = Pool().get('res.user')
|
||
user = User(Transaction().user)
|
||
return "Welcome " + user.name + " !"
|
||
|
||
class Incoming(metaclass=PoolMeta):
|
||
__name__ = 'document.incoming'
|
||
|
||
dashboard = fields.Many2One('purchase.dashboard',"Dashboard")
|
||
|
||
class BotWizard(Wizard):
|
||
"Bot wizard"
|
||
__name__ = "bot.wizard"
|
||
|
||
start = StateTransition()
|
||
|
||
def get_model(self,keyword):
|
||
if keyword == 'purchase':
|
||
return 'purchase.purchase'
|
||
elif keyword == 'sale':
|
||
return 'sale.sale'
|
||
elif 'prepayment' in keyword:
|
||
return 'account.invoice'
|
||
|
||
def transition_start(self):
|
||
Dashboard = Pool().get('purchase.dashboard')
|
||
Party = Pool().get('party.party')
|
||
Account = Pool().get('account.account')
|
||
Currency = Pool().get('currency.currency')
|
||
Purchase = Pool().get('purchase.purchase')
|
||
Sale = Pool().get('sale.sale')
|
||
Invoice = Pool().get('account.invoice')
|
||
InvoiceLine = Pool().get('account.invoice.line')
|
||
StockMove = Pool().get('stock.move')
|
||
ExecutionPlan = Pool().get('workflow.plan')
|
||
DelPeriod = Pool().get('product.month')
|
||
Lot = Pool().get('lot.lot')
|
||
LotQt = Pool().get('lot.qt')
|
||
LotAdd = Pool().get('lot.add.line')
|
||
Date = Pool().get('ir.date')
|
||
d = Dashboard(self.records[0])
|
||
context = Transaction().context
|
||
if d:
|
||
d.action_return = ""
|
||
Dashboard.save([d])
|
||
parts = shlex.split(context['user_request'])
|
||
do_, mo, cp, fl, un, un2, un3 = (parts + [None]*7)[:7]
|
||
if do_ in keywords:
|
||
logger.info("BOT_WIZARD:%s",fl)
|
||
parties = Party.search(['id','>',0])
|
||
party = None
|
||
model = self.get_model(mo)
|
||
if cp:
|
||
for p in parties:
|
||
if cp.lower() == p.name.lower():
|
||
party = p
|
||
break
|
||
if do_ == "duplicate":
|
||
if mo == "help":
|
||
return 'end'
|
||
if mo == "purchase" or mo == "sale":
|
||
if party:
|
||
Model = Pool().get(model)
|
||
if fl:
|
||
line_price = None
|
||
line_period = None
|
||
if un:
|
||
plan = ExecutionPlan.search(['name','=', un])
|
||
if plan:
|
||
models = Model.search([('party','=',party.id),('plan','=',plan[0].id)],order=[('create_date', 'DESC')])
|
||
if un3:
|
||
line_price = Decimal(un3)
|
||
line_period = DelPeriod.search(['month_name','=',un2])[0]
|
||
else:
|
||
if un2.isdigit():
|
||
line_price = Decimal(un2)
|
||
else:
|
||
periods = DelPeriod.search(['month_name','=',un])
|
||
if periods:
|
||
line_period = periods[0]
|
||
if un2.isdigit():
|
||
line_price = Decimal(un2)
|
||
else:
|
||
if un.isdigit():
|
||
line_price = Decimal(un)
|
||
else:
|
||
models = Model.search([('party','=',party.id)],order=[('create_date', 'DESC')])
|
||
if models:
|
||
model_id = models[0].id
|
||
new_model, = Model.copy([model_id], default={})
|
||
Model.save([new_model])
|
||
Model.quote([new_model])
|
||
logger.info("BOT_WIZARD:%s",new_model.lines)
|
||
if new_model.lines and fl:
|
||
line = new_model.lines[0]
|
||
line.quantity = Decimal(fl)
|
||
line.quantity_theorical = Decimal(fl)
|
||
line.premium = Decimal(0)
|
||
if line_period:
|
||
line.del_period = line_period
|
||
if line_price:
|
||
if line.linked_price > 0:
|
||
line.linked_price = line_price
|
||
line.unit_price = line.get_price_linked_currency()
|
||
else:
|
||
line.unit_price = line_price
|
||
Pool().get(mo + '.line').save([line])
|
||
d.action_return = model + ',' + str(new_model.id) + ',' + str(new_model.number)
|
||
Dashboard.save([d])
|
||
elif do_ == "match":
|
||
Lml = Pool().get('lot.matching.lot')
|
||
lmp = Lml()
|
||
lms = Lml()
|
||
p = Purchase.search(['number','=',mo])
|
||
if p:
|
||
p = p[0]
|
||
s = Sale.search(['number','=',cp])
|
||
if s:
|
||
s = s[0]
|
||
lp = p.lines[0].lots[1]
|
||
lmp.lot_id = lp.id
|
||
lqt = lp.getLotQt()
|
||
lmp.lot_r_id = lqt[0].id if lqt else None
|
||
lmp.lot_matched_qt = lp.lot_quantity
|
||
ls = s.lines[0].lots[0]
|
||
lms.lot_r_id = ls.getLotQt()[0].id
|
||
lms.lot_matched_qt = lp.lot_quantity
|
||
lms.lot_sale = s
|
||
LotQt.match_lots([lmp],[lms])
|
||
Sale._process_shipment([s])
|
||
|
||
elif do_ == "add":
|
||
if mo == "lot":
|
||
purchase = Purchase.search([('number','=',cp)])
|
||
if purchase:
|
||
purchase = purchase[0]
|
||
vlot = purchase.lines[0].lots[0]
|
||
lqt = LotQt.search([('lot_p','=',vlot.id)])
|
||
if lqt and vlot.lot_quantity > 0:
|
||
lqt = lqt[0]
|
||
l = LotAdd()
|
||
l.lot_qt = vlot.lot_quantity
|
||
l.lot_unit = purchase.lines[0].unit
|
||
l.lot_unit_line = l.lot_unit
|
||
l.lot_quantity = l.lot_qt
|
||
l.lot_gross_quantity = l.lot_qt
|
||
l.lot_premium = Decimal(0)
|
||
l.lot_chunk_key = None
|
||
lot_id = LotQt.add_physical_lots(lqt,[l])
|
||
d.action_return = 'lot.lot,' + str(lot_id) + ',' + str(lot_id)
|
||
Dashboard.save([d])
|
||
elif do_ == "reception":
|
||
if mo == "lot":
|
||
lot = Lot(int(cp))
|
||
if lot.lot_move:
|
||
StockMove.do([lot.lot_move[0].move])
|
||
d.action_return = 'stock.move,' + str(lot.lot_move[0].move.id) + ',' + str(lot.lot_move[0].move.id)
|
||
Dashboard.save([d])
|
||
elif do_ == "delivery":
|
||
if mo == "lot":
|
||
lot = Lot(int(cp))
|
||
if lot.lot_move:
|
||
StockMove.do([lot.lot_move[1].move])
|
||
d.action_return = 'stock.move,' + str(lot.lot_move[1].move.id) + ',' + str(lot.lot_move[1].move.id)
|
||
Dashboard.save([d])
|
||
elif do_ == "invoice":
|
||
if mo == "lot":
|
||
lot = Lot(int(cp))
|
||
Purchase._process_invoice([lot.line.purchase],[lot],'prov')
|
||
if lot.invoice_line_prov:
|
||
invoice = Invoice(lot.invoice_line_prov.invoice.id)
|
||
invoice.invoice_date = Date.today()
|
||
if fl == "prepayment":
|
||
if un:
|
||
invoice.call_deposit(Account(748),un)
|
||
else:
|
||
invoice.call_deposit(Account(748),'Deposit')
|
||
Invoice.validate_invoice([invoice])
|
||
d.action_return = 'account.invoice,' + str(lot.invoice_line_prov.invoice.id) + ',' + str(lot.invoice_line_prov.invoice.number)
|
||
Dashboard.save([d])
|
||
elif do_ == "pay":
|
||
if mo == "invoice":
|
||
#PayInvoice = Pool().get('account.invoice.pay')
|
||
PaymentMethod = Pool().get('account.invoice.payment.method')
|
||
inv = Invoice.search(['number','=',cp])
|
||
if inv:
|
||
pm = PaymentMethod.search(['id','>',0])[0]
|
||
inv = inv[0]
|
||
lines = inv.pay_invoice(
|
||
inv.amount_to_pay, pm, Date.today(),
|
||
'Payment of invoice ' + inv.number, 0, party=inv.party.id)
|
||
if lines:
|
||
d.action_return = 'account.invoice,' + str(inv.id) + ',' + inv.number
|
||
Dashboard.save([d])
|
||
elif do_ == "create":
|
||
if mo == "prepaymentP" or mo == "prepaymentS":
|
||
if party:
|
||
if party.addresses:
|
||
invoice = Invoice()
|
||
invoice.type = 'in' if mo == "prepaymentP" else 'out'
|
||
invoice.journal = 2 if mo == "prepaymentP" else 1#invoice.set_journal()
|
||
logger.info("WIZARD_PREPAY:%s",invoice.journal)
|
||
invoice.invoice_date = Date.today()
|
||
invoice.party = party.id
|
||
invoice.invoice_address = party.addresses[0]
|
||
invoice.account = party.account_payable_used if mo == "prepaymentP" else party.account_receivable_used
|
||
invoice.payment_term = 6
|
||
invoice.description = 'Prepayment supplier' if mo == "prepaymentP" else 'Prepayment client'
|
||
if un:
|
||
curr = Currency.search(['name','=', un])
|
||
if curr:
|
||
invoice.currency = curr[0].id
|
||
line = InvoiceLine()
|
||
line.account = 748
|
||
line.quantity = 1
|
||
line.unit_price = Decimal(fl)
|
||
invoice.lines = [line]
|
||
Invoice.save([invoice])
|
||
Invoice.post([invoice])
|
||
d.action_return = model + ',' + str(invoice.id)+ ',' + str(invoice.number)
|
||
Dashboard.save([d])
|
||
|
||
return 'end'
|
||
|
||
class BotAction(ModelSQL, ModelView):
|
||
"Bot Action"
|
||
__name__ = "bot.action"
|
||
|
||
dashboard = fields.Many2One('purchase.dashboard',"Dashboard")
|
||
type = fields.Selection([
|
||
('duplicate', 'duplicate'),
|
||
('invoice', 'invoice'),
|
||
('receive', 'receive'),
|
||
('add physic', 'add physic'),
|
||
('match', 'match'),
|
||
('link', 'link'),
|
||
('create', 'create')])
|
||
model = fields.Char("Model")
|
||
am_qt = fields.Numeric("Amount/Quantity")
|
||
unit = fields.Char("Unit/Currency")
|
||
keyword = fields.Char("Keyword")
|
||
cp = fields.Char("Party")
|
||
model_id = fields.Integer("Model id")
|
||
res_id = fields.Integer("Res id")
|
||
state = fields.Selection([
|
||
('todo', 'todo'),
|
||
('done', 'done'),
|
||
])
|
||
|
||
@classmethod
|
||
def state_default(cls):
|
||
return 'todo'
|
||
|
||
@classmethod
|
||
def state_dashboard(cls):
|
||
return 1 |