feat(social): Implement Real Meta API, Webhook Controller, Auto-Reply tests, AI Sentiment analysis and Security Rules
This commit is contained in:
@@ -1 +1,3 @@
|
||||
from . import models
|
||||
from . import controllers
|
||||
from . import wizard
|
||||
|
||||
@@ -22,10 +22,32 @@ Features:
|
||||
""",
|
||||
'author': 'MyMach',
|
||||
'website': 'https://www.mymach.com',
|
||||
'depends': ['base', 'web', 'crm', 'mail'],
|
||||
'depends': ['base', 'mail', 'social_media', 'utm', 'link_tracker', 'crm', 'base_setup', 'product'],
|
||||
'data': [
|
||||
'security/security.xml',
|
||||
'security/ir_rule.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/social_post_cron.xml',
|
||||
'data/social_competitor_cron.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/social_analytics_views.xml',
|
||||
'views/social_live_post_views.xml',
|
||||
'views/social_post_views.xml',
|
||||
'wizard/social_ai_wizard_views.xml',
|
||||
'views/social_stream_views.xml',
|
||||
'views/social_media_views.xml',
|
||||
'views/social_auto_reply_views.xml',
|
||||
'views/social_auto_reply_log_views.xml',
|
||||
'views/social_stream_post_kanban_views.xml',
|
||||
'views/social_menu.xml',
|
||||
'views/social_account_views.xml',
|
||||
'views/social_competitor_views.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'mymach_social_nextgen/static/src/css/social_dashboard.css',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
|
||||
6
extra-addons/mymach_social_nextgen/api/__init__.py
Normal file
6
extra-addons/mymach_social_nextgen/api/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from . import meta_api
|
||||
from . import instagram_api
|
||||
from . import linkedin_api
|
||||
from . import x_api
|
||||
from . import youtube_api
|
||||
from . import tiktok_api
|
||||
36
extra-addons/mymach_social_nextgen/api/instagram_api.py
Normal file
36
extra-addons/mymach_social_nextgen/api/instagram_api.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import requests
|
||||
from urllib.parse import urlencode
|
||||
|
||||
class InstagramAPI:
|
||||
"""Helper class for direct Instagram OAuth (Basic Display or Native Auth)."""
|
||||
|
||||
AUTH_URL = "https://api.instagram.com/oauth/authorize"
|
||||
TOKEN_URL = "https://api.instagram.com/oauth/access_token"
|
||||
GRAPH_URL = "https://graph.instagram.com"
|
||||
|
||||
def __init__(self, client_id, client_secret, redirect_uri):
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
def get_auth_url(self, state):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'state': state,
|
||||
'scope': 'user_profile,user_media',
|
||||
'response_type': 'code'
|
||||
}
|
||||
return f"{self.AUTH_URL}?{urlencode(params)}"
|
||||
|
||||
def exchange_code_for_token(self, code):
|
||||
data = {
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'grant_type': 'authorization_code',
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'code': code
|
||||
}
|
||||
response = requests.post(self.TOKEN_URL, data=data, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
36
extra-addons/mymach_social_nextgen/api/linkedin_api.py
Normal file
36
extra-addons/mymach_social_nextgen/api/linkedin_api.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import requests
|
||||
from urllib.parse import urlencode
|
||||
|
||||
class LinkedInAPI:
|
||||
"""Helper class for LinkedIn API."""
|
||||
|
||||
AUTH_URL = "https://www.linkedin.com/oauth/v2/authorization"
|
||||
TOKEN_URL = "https://www.linkedin.com/oauth/v2/accessToken"
|
||||
|
||||
def __init__(self, client_id, client_secret, redirect_uri):
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
def get_auth_url(self, state):
|
||||
params = {
|
||||
'response_type': 'code',
|
||||
'client_id': self.client_id,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'state': state,
|
||||
'scope': 'w_member_social r_liteprofile w_organization_social r_organization_social'
|
||||
}
|
||||
return f"{self.AUTH_URL}?{urlencode(params)}"
|
||||
|
||||
def exchange_code_for_token(self, code):
|
||||
data = {
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'redirect_uri': self.redirect_uri
|
||||
}
|
||||
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
response = requests.post(self.TOKEN_URL, data=data, headers=headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
69
extra-addons/mymach_social_nextgen/api/meta_api.py
Normal file
69
extra-addons/mymach_social_nextgen/api/meta_api.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import requests
|
||||
from urllib.parse import urlencode
|
||||
|
||||
class MetaAPI:
|
||||
"""Helper class for Meta (Facebook/Instagram) Graph API."""
|
||||
|
||||
AUTH_URL = "https://www.facebook.com/v19.0/dialog/oauth"
|
||||
TOKEN_URL = "https://graph.facebook.com/v19.0/oauth/access_token"
|
||||
GRAPH_URL = "https://graph.facebook.com/v19.0"
|
||||
|
||||
def __init__(self, client_id, client_secret, redirect_uri):
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
def get_auth_url(self, state):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'state': state,
|
||||
'scope': 'pages_show_list,pages_read_engagement,pages_manage_posts,instagram_basic,instagram_content_publish',
|
||||
'response_type': 'code'
|
||||
}
|
||||
return f"{self.AUTH_URL}?{urlencode(params)}"
|
||||
|
||||
def exchange_code_for_token(self, code):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'code': code
|
||||
}
|
||||
response = requests.get(self.TOKEN_URL, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def get_profile_info(self, access_token):
|
||||
"""Kullanıcının yönettiği sayfaları ve hesap ID'lerini çeker."""
|
||||
url = f"{self.GRAPH_URL}/me/accounts"
|
||||
params = {'access_token': access_token}
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def publish_post(self, page_id, access_token, message, link=None):
|
||||
"""Belirtilen sayfada gönderi paylaşır."""
|
||||
url = f"{self.GRAPH_URL}/{page_id}/feed"
|
||||
payload = {
|
||||
'message': message,
|
||||
'access_token': access_token
|
||||
}
|
||||
if link:
|
||||
payload['link'] = link
|
||||
|
||||
response = requests.post(url, data=payload, timeout=15)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def refresh_token(self, short_lived_token):
|
||||
"""Kısa ömürlü token'ı uzun ömürlü (60 günlük) token'a çevirir veya süresini uzatır."""
|
||||
params = {
|
||||
'grant_type': 'fb_exchange_token',
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'fb_exchange_token': short_lived_token
|
||||
}
|
||||
response = requests.get(self.TOKEN_URL, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
38
extra-addons/mymach_social_nextgen/api/tiktok_api.py
Normal file
38
extra-addons/mymach_social_nextgen/api/tiktok_api.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import requests
|
||||
from urllib.parse import urlencode
|
||||
|
||||
class TikTokAPI:
|
||||
"""Helper class for TikTok Developer API / OAuth 2.0."""
|
||||
|
||||
AUTH_URL = "https://www.tiktok.com/v2/auth/authorize/"
|
||||
TOKEN_URL = "https://open.tiktokapis.com/v2/oauth/token/"
|
||||
|
||||
def __init__(self, client_id, client_secret, redirect_uri):
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
def get_auth_url(self, state):
|
||||
params = {
|
||||
'client_key': self.client_id,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'state': state,
|
||||
'scope': 'user.info.basic,video.list,video.upload',
|
||||
'response_type': 'code'
|
||||
}
|
||||
return f"{self.AUTH_URL}?{urlencode(params)}"
|
||||
|
||||
def exchange_code_for_token(self, code):
|
||||
data = {
|
||||
'client_key': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'grant_type': 'authorization_code',
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'code': code
|
||||
}
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
response = requests.post(self.TOKEN_URL, data=data, headers=headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
45
extra-addons/mymach_social_nextgen/api/x_api.py
Normal file
45
extra-addons/mymach_social_nextgen/api/x_api.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import requests
|
||||
import base64
|
||||
from urllib.parse import urlencode
|
||||
|
||||
class XAPI:
|
||||
"""Helper class for X (Twitter) OAuth 2.0 API."""
|
||||
|
||||
AUTH_URL = "https://twitter.com/i/oauth2/authorize"
|
||||
TOKEN_URL = "https://api.twitter.com/2/oauth2/token"
|
||||
|
||||
def __init__(self, client_id, client_secret, redirect_uri):
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
def get_auth_url(self, state, code_challenge="challenge"):
|
||||
# Note: X requires PKCE. For a complete implementation, a real code_verifier and code_challenge should be generated.
|
||||
params = {
|
||||
'response_type': 'code',
|
||||
'client_id': self.client_id,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'scope': 'tweet.read tweet.write users.read offline.access',
|
||||
'state': state,
|
||||
'code_challenge': code_challenge,
|
||||
'code_challenge_method': 'plain'
|
||||
}
|
||||
return f"{self.AUTH_URL}?{urlencode(params)}"
|
||||
|
||||
def exchange_code_for_token(self, code, code_verifier="challenge"):
|
||||
data = {
|
||||
'grant_type': 'authorization_code',
|
||||
'client_id': self.client_id,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'code': code,
|
||||
'code_verifier': code_verifier
|
||||
}
|
||||
auth_string = f"{self.client_id}:{self.client_secret}"
|
||||
b64_auth = base64.b64encode(auth_string.encode('ascii')).decode('ascii')
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': f'Basic {b64_auth}'
|
||||
}
|
||||
response = requests.post(self.TOKEN_URL, data=data, headers=headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
37
extra-addons/mymach_social_nextgen/api/youtube_api.py
Normal file
37
extra-addons/mymach_social_nextgen/api/youtube_api.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import requests
|
||||
from urllib.parse import urlencode
|
||||
|
||||
class YouTubeAPI:
|
||||
"""Helper class for Google/YouTube Data API v3."""
|
||||
|
||||
AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
|
||||
TOKEN_URL = "https://oauth2.googleapis.com/token"
|
||||
|
||||
def __init__(self, client_id, client_secret, redirect_uri):
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.redirect_uri = redirect_uri
|
||||
|
||||
def get_auth_url(self, state):
|
||||
params = {
|
||||
'client_id': self.client_id,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'response_type': 'code',
|
||||
'scope': 'https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/youtube.readonly',
|
||||
'access_type': 'offline',
|
||||
'prompt': 'consent',
|
||||
'state': state
|
||||
}
|
||||
return f"{self.AUTH_URL}?{urlencode(params)}"
|
||||
|
||||
def exchange_code_for_token(self, code):
|
||||
data = {
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'redirect_uri': self.redirect_uri
|
||||
}
|
||||
response = requests.post(self.TOKEN_URL, data=data, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import main
|
||||
from . import webhook_controller
|
||||
76
extra-addons/mymach_social_nextgen/controllers/main.py
Normal file
76
extra-addons/mymach_social_nextgen/controllers/main.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
import werkzeug
|
||||
|
||||
class SocialMediaController(http.Controller):
|
||||
|
||||
@http.route('/api/social/callback/<string:platform>', type='http', auth='user', website=True)
|
||||
def social_oauth_callback(self, platform, **kw):
|
||||
"""
|
||||
OAuth2 callback from social platforms.
|
||||
Expected parameters usually include 'code' and sometimes 'state' (which can contain our account ID).
|
||||
"""
|
||||
code = kw.get('code')
|
||||
state = kw.get('state')
|
||||
error = kw.get('error')
|
||||
|
||||
if error:
|
||||
return request.render('http_routing.http_error', {'status_code': 400, 'status_message': f'OAuth Error: {error}'})
|
||||
|
||||
if not code:
|
||||
return request.render('http_routing.http_error', {'status_code': 400, 'status_message': 'No authorization code received.'})
|
||||
|
||||
# Typically, state contains the ID of the social.account record we are trying to link.
|
||||
account_id = None
|
||||
try:
|
||||
if state:
|
||||
account_id = int(state)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if not account_id:
|
||||
return "Hata: State parametresi (account_id) bulunamadı."
|
||||
|
||||
account = request.env['mymach.social.account'].browse(account_id)
|
||||
if not account.exists():
|
||||
return request.render('http_routing.http_error', {'status_code': 404, 'status_message': 'Social account record not found.'})
|
||||
|
||||
# Process the code based on the platform
|
||||
try:
|
||||
account.process_oauth_callback(code)
|
||||
|
||||
# Redirect back to the account form view
|
||||
action = request.env.ref('mymach_social_nextgen.action_social_account')
|
||||
url = f"/web#id={account.id}&model=mymach.social.account&view_type=form&action={action.id}"
|
||||
return werkzeug.utils.redirect(url)
|
||||
|
||||
except Exception as e:
|
||||
return request.render('http_routing.http_error', {'status_code': 500, 'status_message': f'Error processing callback: {str(e)}'})
|
||||
|
||||
@http.route(['/social/meta/webhook'], type='json', auth="public", methods=['GET', 'POST'], csrf=False)
|
||||
def meta_webhook(self, **post):
|
||||
"""
|
||||
Endpoint to receive real-time updates from Meta (Instagram/Facebook).
|
||||
Supports GET for verification, POST for receiving data.
|
||||
"""
|
||||
if request.httprequest.method == 'GET':
|
||||
# Meta verification logic
|
||||
verify_token = request.env['ir.config_parameter'].sudo().get_param('mymach_social.meta_verify_token')
|
||||
mode = request.params.get('hub.mode')
|
||||
token = request.params.get('hub.verify_token')
|
||||
challenge = request.params.get('hub.challenge')
|
||||
|
||||
if mode and token:
|
||||
if mode == 'subscribe' and token == verify_token:
|
||||
return werkzeug.wrappers.Response(challenge, status=200)
|
||||
else:
|
||||
return werkzeug.wrappers.Response('Forbidden', status=403)
|
||||
return werkzeug.wrappers.Response('Invalid Request', status=400)
|
||||
|
||||
elif request.httprequest.method == 'POST':
|
||||
# Receive Webhook Payload (Comments, Messages, Likes)
|
||||
data = request.jsonrequest
|
||||
# Normalde burada data parse edilir ve mymach.social.stream.post kayıtları oluşturulur.
|
||||
# Şimdilik canlı olmadığı için logluyoruz.
|
||||
# _logger.info("Meta Webhook Received: %s", data)
|
||||
return {'status': 'success'}
|
||||
@@ -0,0 +1,74 @@
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
import json
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class MetaWebhookController(http.Controller):
|
||||
|
||||
@http.route('/api/social/webhook/meta', type='http', auth='public', methods=['GET', 'POST'], csrf=False)
|
||||
def meta_webhook(self, **kw):
|
||||
"""
|
||||
Meta (Facebook/Instagram) Webhook Endpoint.
|
||||
GET isteği webhook verification (doğrulama) içindir.
|
||||
POST isteği ise gerçek zamanlı yorum veya mesaj olaylarını (events) alır.
|
||||
"""
|
||||
if request.httprequest.method == 'GET':
|
||||
# Verification for Meta Webhook setup
|
||||
verify_token = request.env['ir.config_parameter'].sudo().get_param('mymach_social.meta_webhook_token', 'default_mymach_token')
|
||||
mode = kw.get('hub.mode')
|
||||
token = kw.get('hub.verify_token')
|
||||
challenge = kw.get('hub.challenge')
|
||||
|
||||
if mode == 'subscribe' and token == verify_token:
|
||||
_logger.info("Meta Webhook başarıyla doğrulandı.")
|
||||
return request.make_response(challenge, headers=[('Content-Type', 'text/plain')])
|
||||
else:
|
||||
_logger.warning("Meta Webhook doğrulama hatası!")
|
||||
return request.make_response('Verification failed', status=403)
|
||||
|
||||
elif request.httprequest.method == 'POST':
|
||||
# Handle incoming webhook event
|
||||
try:
|
||||
data = json.loads(request.httprequest.data)
|
||||
_logger.info(f"Meta Webhook veri alındı: {data}")
|
||||
|
||||
if data.get('object') == 'page':
|
||||
for entry in data.get('entry', []):
|
||||
page_id = entry.get('id')
|
||||
# Hesabı (Sayfayı) bul
|
||||
account = request.env['mymach.social.account'].sudo().search([('platform_account_id', '=', page_id), ('platform', '=', 'facebook')], limit=1)
|
||||
if not account:
|
||||
_logger.warning(f"Hesap bulunamadı (Page ID: {page_id})")
|
||||
continue
|
||||
|
||||
# İlgili stream'i bul (Feed stream)
|
||||
stream = request.env['mymach.social.stream'].sudo().search([('account_id', '=', account.id), ('stream_type', '=', 'feed')], limit=1)
|
||||
if not stream:
|
||||
continue
|
||||
|
||||
for messaging_event in entry.get('changes', []):
|
||||
value = messaging_event.get('value', {})
|
||||
item = value.get('item')
|
||||
|
||||
# Sadece yorumları işle (basit örnek)
|
||||
if item == 'comment' and value.get('verb') == 'add':
|
||||
message_text = value.get('message')
|
||||
author_name = value.get('from', {}).get('name', 'Bilinmeyen Kullanıcı')
|
||||
post_id = value.get('post_id')
|
||||
|
||||
# Odoo'da yeni Stream Post oluştur (bu otomatik yanıtları da tetikler)
|
||||
request.env['mymach.social.stream.post'].sudo().create({
|
||||
'stream_id': stream.id,
|
||||
'author_name': author_name,
|
||||
'message': message_text,
|
||||
'message_type': 'comment',
|
||||
'platform_post_id': value.get('comment_id')
|
||||
})
|
||||
|
||||
return request.make_response('EVENT_RECEIVED', status=200)
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(f"Meta Webhook işlenirken hata oluştu: {str(e)}")
|
||||
return request.make_response('Error processing webhook', status=500)
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="ir_cron_scrape_competitors" model="ir.cron">
|
||||
<field name="name">Sosyal Medya: Rakip Analizi Veri Kazıma</field>
|
||||
<field name="model_id" ref="model_social_competitor"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_scrape_competitors()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
15
extra-addons/mymach_social_nextgen/data/social_post_cron.xml
Normal file
15
extra-addons/mymach_social_nextgen/data/social_post_cron.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- Cron Job to Publish Scheduled Posts -->
|
||||
<record id="ir_cron_publish_scheduled_posts" model="ir.cron">
|
||||
<field name="name">Social Media: Publish Scheduled Posts</field>
|
||||
<field name="model_id" ref="model_mymach_social_post"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_publish_scheduled_posts()</field>
|
||||
<field name="interval_number">15</field>
|
||||
<field name="interval_type">minutes</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
517
extra-addons/mymach_social_nextgen/i18n/tr.po
Normal file
517
extra-addons/mymach_social_nextgen/i18n/tr.po
Normal file
@@ -0,0 +1,517 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mymach_social_nextgen
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 19.0-20260723\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-27 10:14+0000\n"
|
||||
"PO-Revision-Date: 2026-07-27 10:14+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__is_ai_generated
|
||||
msgid "AI Generated"
|
||||
msgstr "Yapay Zeka ile Üretildi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model_terms:ir.ui.view,arch_db:mymach_social_nextgen.view_social_account_form
|
||||
msgid "API Settings"
|
||||
msgstr "API Ayarları"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__access_token
|
||||
msgid "Access Token"
|
||||
msgstr "Erişim Jetonu"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__name
|
||||
msgid "Account Name"
|
||||
msgstr "Hesap Adı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_needaction
|
||||
msgid "Action Needed"
|
||||
msgstr "Eylem Gerekiyor"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__active
|
||||
msgid "Active"
|
||||
msgstr "Aktif"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_ids
|
||||
msgid "Activities"
|
||||
msgstr "Aktiviteler"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_exception_decoration
|
||||
msgid "Activity Exception Decoration"
|
||||
msgstr "Aktivite İstisnası Dekorasyonu"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_state
|
||||
msgid "Activity State"
|
||||
msgstr "Aktivite Durumu"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_type_icon
|
||||
msgid "Activity Type Icon"
|
||||
msgstr "Aktivite Türü İkonu"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__notes
|
||||
msgid "Analysis Notes"
|
||||
msgstr "Analiz Notları"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_analytics
|
||||
msgid "Analytics"
|
||||
msgstr "Analiz"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_attachment_count
|
||||
msgid "Attachment Count"
|
||||
msgstr "Ek Sayısı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__name
|
||||
msgid "Competitor Name"
|
||||
msgstr "Rakip Adı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_configuration
|
||||
msgid "Configuration"
|
||||
msgstr "Yapılandırma"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__content
|
||||
msgid "Content"
|
||||
msgstr "İçerik"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model_terms:ir.actions.act_window,help:mymach_social_nextgen.action_social_account
|
||||
msgid "Create your first Social Media Account connection!"
|
||||
msgstr "İlk Sosyal Medya Hesabı bağlantınızı oluşturun!"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__create_uid
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__create_uid
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__create_uid
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__create_uid
|
||||
msgid "Created by"
|
||||
msgstr "Oluşturan"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__create_date
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__create_date
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__create_date
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__create_date
|
||||
msgid "Created on"
|
||||
msgstr "Oluşturulma Tarihi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_dashboard
|
||||
msgid "Dashboard"
|
||||
msgstr "Kontrol Paneli"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__display_name
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__display_name
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__display_name
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Görünen Ad"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__draft
|
||||
msgid "Draft"
|
||||
msgstr "Taslak"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_media__media_type__template
|
||||
msgid "Dynamic Template"
|
||||
msgstr "Dinamik Şablon"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__engagement_rate
|
||||
msgid "Engagement Rate (%)"
|
||||
msgstr "Etkileşim Oranı (%)"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__error_message
|
||||
msgid "Error Message"
|
||||
msgstr "Hata Mesajı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__facebook
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__facebook
|
||||
msgid "Facebook"
|
||||
msgstr "Facebook"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__failed
|
||||
msgid "Failed"
|
||||
msgstr "Başarısız"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__file_data
|
||||
msgid "File"
|
||||
msgstr "Dosya"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__name
|
||||
msgid "File Name"
|
||||
msgstr "Dosya Adı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_follower_ids
|
||||
msgid "Followers"
|
||||
msgstr "Takipçiler"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_partner_ids
|
||||
msgid "Followers (Partners)"
|
||||
msgstr "Takipçiler (İş Ortakları)"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__followers_count
|
||||
msgid "Followers Count"
|
||||
msgstr "Takipçi Sayısı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__activity_type_icon
|
||||
msgid "Font awesome icon e.g. fa-tasks"
|
||||
msgstr "Font awesome ikonu örn. fa-tasks"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__has_message
|
||||
msgid "Has Message"
|
||||
msgstr "Mesaj Var"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__id
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__id
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__id
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_exception_icon
|
||||
msgid "Icon"
|
||||
msgstr "İkon"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__activity_exception_icon
|
||||
msgid "Icon to indicate an exception activity."
|
||||
msgstr "Bir istisna aktivitesini gösteren ikon."
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_needaction
|
||||
msgid "If checked, new messages require your attention."
|
||||
msgstr "İşaretliyse, yeni mesajlar dikkatinizi gerektirir."
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_has_error
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_has_sms_error
|
||||
msgid "If checked, some messages have a delivery error."
|
||||
msgstr "İşaretliyse, bazı mesajlarda teslimat hatası var."
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_media__media_type__image
|
||||
msgid "Image"
|
||||
msgstr "Görsel"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__instagram
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__instagram
|
||||
msgid "Instagram"
|
||||
msgstr "Instagram"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_is_follower
|
||||
msgid "Is Follower"
|
||||
msgstr "Takipçi mi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__last_scraped
|
||||
msgid "Last Scraped Date"
|
||||
msgstr "Son Taranma Tarihi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__write_uid
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__write_uid
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__write_uid
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "Son Güncelleyen"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__write_date
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__write_date
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__write_date
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Son Güncellenme Tarihi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__linkedin
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__linkedin
|
||||
msgid "LinkedIn"
|
||||
msgstr "LinkedIn"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__media_ids
|
||||
msgid "Media Files"
|
||||
msgstr "Medya Dosyaları"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_media
|
||||
msgid "Media Library"
|
||||
msgstr "Medya Kütüphanesi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__media_type
|
||||
msgid "Media Type"
|
||||
msgstr "Medya Türü"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_has_error
|
||||
msgid "Message Delivery error"
|
||||
msgstr "Mesaj Teslimat hatası"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_ids
|
||||
msgid "Messages"
|
||||
msgstr "Mesajlar"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__mimetype
|
||||
msgid "Mime Type"
|
||||
msgstr "Mime Türü"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__my_activity_date_deadline
|
||||
msgid "My Activity Deadline"
|
||||
msgstr "Aktivite Son Tarihim"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_root
|
||||
msgid "MyMach Social"
|
||||
msgstr "MyMach Social"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_calendar_event_id
|
||||
msgid "Next Activity Calendar Event"
|
||||
msgstr "Sonraki Aktivite Takvim Etkinliği"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_date_deadline
|
||||
msgid "Next Activity Deadline"
|
||||
msgstr "Sonraki Aktivite Son Tarihi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_summary
|
||||
msgid "Next Activity Summary"
|
||||
msgstr "Sonraki Aktivite Özeti"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_type_id
|
||||
msgid "Next Activity Type"
|
||||
msgstr "Sonraki Aktivite Türü"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_needaction_counter
|
||||
msgid "Number of Actions"
|
||||
msgstr "Eylem Sayısı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_has_error_counter
|
||||
msgid "Number of errors"
|
||||
msgstr "Hata sayısı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_needaction_counter
|
||||
msgid "Number of messages requiring action"
|
||||
msgstr "Eylem gerektiren mesaj sayısı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__message_has_error_counter
|
||||
msgid "Number of messages with delivery error"
|
||||
msgstr "Teslimat hatası olan mesaj sayısı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__platform
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__platform
|
||||
msgid "Platform"
|
||||
msgstr "Platform"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__platform_account_id
|
||||
msgid "Platform Account ID"
|
||||
msgstr "Platform Hesap ID"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__name
|
||||
msgid "Post Title"
|
||||
msgstr "Gönderi Başlığı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_posts
|
||||
msgid "Posts"
|
||||
msgstr "Gönderiler"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__profile_image_url
|
||||
msgid "Profile Image URL"
|
||||
msgstr "Profil Resmi URL"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_competitor__profile_url
|
||||
msgid "Profile URL"
|
||||
msgstr "Profil URL"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__published
|
||||
msgid "Published"
|
||||
msgstr "Yayınlandı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__published_date
|
||||
msgid "Published Date"
|
||||
msgstr "Yayınlanma Tarihi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__refresh_token
|
||||
msgid "Refresh Token"
|
||||
msgstr "Yenileme Jetonu"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__activity_user_id
|
||||
msgid "Responsible User"
|
||||
msgstr "Sorumlu Kullanıcı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__message_has_sms_error
|
||||
msgid "SMS Delivery error"
|
||||
msgstr "SMS Teslimat hatası"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__scheduled
|
||||
msgid "Scheduled"
|
||||
msgstr "Planlandı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__scheduled_date
|
||||
msgid "Scheduled Date"
|
||||
msgstr "Planlanma Tarihi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model_terms:ir.ui.view,arch_db:mymach_social_nextgen.view_social_account_form
|
||||
msgid "Social Account"
|
||||
msgstr "Sosyal Hesap"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.actions.act_window,name:mymach_social_nextgen.action_social_account
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__account_ids
|
||||
#: model:ir.ui.menu,name:mymach_social_nextgen.menu_social_accounts
|
||||
#: model_terms:ir.ui.view,arch_db:mymach_social_nextgen.view_social_account_tree
|
||||
msgid "Social Accounts"
|
||||
msgstr "Sosyal Hesaplar"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model,name:mymach_social_nextgen.model_social_account
|
||||
msgid "Social Media Account"
|
||||
msgstr "Sosyal Medya Hesabı"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model,name:mymach_social_nextgen.model_social_competitor
|
||||
msgid "Social Media Competitor Analysis"
|
||||
msgstr "Sosyal Medya Rakip Analizi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model,name:mymach_social_nextgen.model_social_media
|
||||
msgid "Social Media Library"
|
||||
msgstr "Sosyal Medya Kütüphanesi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model,name:mymach_social_nextgen.model_social_post
|
||||
msgid "Social Media Post"
|
||||
msgstr "Sosyal Medya Gönderisi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_media__template_id
|
||||
msgid "Source Template"
|
||||
msgstr "Kaynak Şablon"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__state
|
||||
msgid "Status"
|
||||
msgstr "Durum"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__activity_state
|
||||
msgid ""
|
||||
"Status based on activities\n"
|
||||
"Overdue: Due date is already passed\n"
|
||||
"Today: Activity date is today\n"
|
||||
"Planned: Future activities."
|
||||
msgstr ""
|
||||
"Aktivitelere dayalı durum\n"
|
||||
"Gecikmiş: Bitiş tarihi çoktan geçti\n"
|
||||
"Bugün: Aktivite tarihi bugün\n"
|
||||
"Planlanan: Gelecekteki aktiviteler."
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_account__token_expiration
|
||||
msgid "Token Expiration Date"
|
||||
msgstr "Jeton Bitiş Tarihi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__activity_exception_decoration
|
||||
msgid "Type of the exception activity on record."
|
||||
msgstr "Kayıttaki istisna aktivitesinin türü."
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_media__media_type__video
|
||||
msgid "Video"
|
||||
msgstr "Video"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_post__state__approval
|
||||
msgid "Waiting for Approval"
|
||||
msgstr "Onay Bekliyor"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,field_description:mymach_social_nextgen.field_social_post__website_message_ids
|
||||
msgid "Website Messages"
|
||||
msgstr "Web Sitesi Mesajları"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields,help:mymach_social_nextgen.field_social_post__website_message_ids
|
||||
msgid "Website communication history"
|
||||
msgstr "Web sitesi iletişim geçmişi"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__x
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__x
|
||||
msgid "X (Twitter)"
|
||||
msgstr "X (Twitter)"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_account__platform__youtube
|
||||
#: model:ir.model.fields.selection,name:mymach_social_nextgen.selection__social_competitor__platform__youtube
|
||||
msgid "YouTube"
|
||||
msgstr "YouTube"
|
||||
|
||||
#. module: mymach_social_nextgen
|
||||
#: model_terms:ir.ui.view,arch_db:mymach_social_nextgen.view_social_account_form
|
||||
msgid "e.g. MyMach Official Facebook"
|
||||
msgstr "örn. MyMach Resmi Facebook"
|
||||
113
extra-addons/mymach_social_nextgen/i18n/translate.py
Normal file
113
extra-addons/mymach_social_nextgen/i18n/translate.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import re
|
||||
|
||||
translations = {
|
||||
"AI Generated": "Yapay Zeka ile Üretildi",
|
||||
"API Settings": "API Ayarları",
|
||||
"Access Token": "Erişim Jetonu",
|
||||
"Account Name": "Hesap Adı",
|
||||
"Action Needed": "Eylem Gerekiyor",
|
||||
"Active": "Aktif",
|
||||
"Activities": "Aktiviteler",
|
||||
"Activity Exception Decoration": "Aktivite İstisnası Dekorasyonu",
|
||||
"Activity State": "Aktivite Durumu",
|
||||
"Activity Type Icon": "Aktivite Türü İkonu",
|
||||
"Analysis Notes": "Analiz Notları",
|
||||
"Analytics": "Analiz",
|
||||
"Attachment Count": "Ek Sayısı",
|
||||
"Competitor Name": "Rakip Adı",
|
||||
"Configuration": "Yapılandırma",
|
||||
"Content": "İçerik",
|
||||
"Create your first Social Media Account connection!": "İlk Sosyal Medya Hesabı bağlantınızı oluşturun!",
|
||||
"Created by": "Oluşturan",
|
||||
"Created on": "Oluşturulma Tarihi",
|
||||
"Dashboard": "Kontrol Paneli",
|
||||
"Display Name": "Görünen Ad",
|
||||
"Draft": "Taslak",
|
||||
"Dynamic Template": "Dinamik Şablon",
|
||||
"Engagement Rate (%)": "Etkileşim Oranı (%)",
|
||||
"Error Message": "Hata Mesajı",
|
||||
"Facebook": "Facebook",
|
||||
"Failed": "Başarısız",
|
||||
"File": "Dosya",
|
||||
"File Name": "Dosya Adı",
|
||||
"Followers": "Takipçiler",
|
||||
"Followers (Partners)": "Takipçiler (İş Ortakları)",
|
||||
"Followers Count": "Takipçi Sayısı",
|
||||
"Font awesome icon e.g. fa-tasks": "Font awesome ikonu örn. fa-tasks",
|
||||
"Has Message": "Mesaj Var",
|
||||
"ID": "ID",
|
||||
"Icon": "İkon",
|
||||
"Icon to indicate an exception activity.": "Bir istisna aktivitesini gösteren ikon.",
|
||||
"If checked, new messages require your attention.": "İşaretliyse, yeni mesajlar dikkatinizi gerektirir.",
|
||||
"If checked, some messages have a delivery error.": "İşaretliyse, bazı mesajlarda teslimat hatası var.",
|
||||
"Image": "Görsel",
|
||||
"Instagram": "Instagram",
|
||||
"Is Follower": "Takipçi mi",
|
||||
"Last Scraped Date": "Son Taranma Tarihi",
|
||||
"Last Updated by": "Son Güncelleyen",
|
||||
"Last Updated on": "Son Güncellenme Tarihi",
|
||||
"LinkedIn": "LinkedIn",
|
||||
"Media Files": "Medya Dosyaları",
|
||||
"Media Library": "Medya Kütüphanesi",
|
||||
"Media Type": "Medya Türü",
|
||||
"Message Delivery error": "Mesaj Teslimat hatası",
|
||||
"Messages": "Mesajlar",
|
||||
"Mime Type": "Mime Türü",
|
||||
"My Activity Deadline": "Aktivite Son Tarihim",
|
||||
"MyMach Social": "MyMach Social",
|
||||
"Next Activity Calendar Event": "Sonraki Aktivite Takvim Etkinliği",
|
||||
"Next Activity Deadline": "Sonraki Aktivite Son Tarihi",
|
||||
"Next Activity Summary": "Sonraki Aktivite Özeti",
|
||||
"Next Activity Type": "Sonraki Aktivite Türü",
|
||||
"Number of Actions": "Eylem Sayısı",
|
||||
"Number of errors": "Hata sayısı",
|
||||
"Number of messages requiring action": "Eylem gerektiren mesaj sayısı",
|
||||
"Number of messages with delivery error": "Teslimat hatası olan mesaj sayısı",
|
||||
"Platform": "Platform",
|
||||
"Platform Account ID": "Platform Hesap ID",
|
||||
"Post Title": "Gönderi Başlığı",
|
||||
"Posts": "Gönderiler",
|
||||
"Profile Image URL": "Profil Resmi URL",
|
||||
"Profile URL": "Profil URL",
|
||||
"Published": "Yayınlandı",
|
||||
"Published Date": "Yayınlanma Tarihi",
|
||||
"Refresh Token": "Yenileme Jetonu",
|
||||
"Responsible User": "Sorumlu Kullanıcı",
|
||||
"SMS Delivery error": "SMS Teslimat hatası",
|
||||
"Scheduled": "Planlandı",
|
||||
"Scheduled Date": "Planlanma Tarihi",
|
||||
"Social Account": "Sosyal Hesap",
|
||||
"Social Accounts": "Sosyal Hesaplar",
|
||||
"Social Media Account": "Sosyal Medya Hesabı",
|
||||
"Social Media Competitor Analysis": "Sosyal Medya Rakip Analizi",
|
||||
"Social Media Library": "Sosyal Medya Kütüphanesi",
|
||||
"Social Media Post": "Sosyal Medya Gönderisi",
|
||||
"Source Template": "Kaynak Şablon",
|
||||
"Status": "Durum",
|
||||
"Status based on activities\\nOverdue: Due date is already passed\\nToday: Activity date is today\\nPlanned: Future activities.": "Aktivitelere dayalı durum\\nGecikmiş: Bitiş tarihi çoktan geçti\\nBugün: Aktivite tarihi bugün\\nPlanlanan: Gelecekteki aktiviteler.",
|
||||
"Token Expiration Date": "Jeton Bitiş Tarihi",
|
||||
"Type of the exception activity on record.": "Kayıttaki istisna aktivitesinin türü.",
|
||||
"Video": "Video",
|
||||
"Waiting for Approval": "Onay Bekliyor",
|
||||
"Website Messages": "Web Sitesi Mesajları",
|
||||
"Website communication history": "Web sitesi iletişim geçmişi",
|
||||
"X (Twitter)": "X (Twitter)",
|
||||
"YouTube": "YouTube",
|
||||
"e.g. MyMach Official Facebook": "örn. MyMach Resmi Facebook"
|
||||
}
|
||||
|
||||
file_path = "c:/Users/Ortak/Desktop/mymach_social_nextgen/extra-addons/mymach_social_nextgen/i18n/tr.po"
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
for eng, tr in translations.items():
|
||||
# Replacing single-line msgid
|
||||
pattern = r'msgid "' + re.escape(eng) + r'"\nmsgstr ""'
|
||||
replacement = 'msgid "' + eng + '"\nmsgstr "' + tr + '"'
|
||||
content = re.sub(pattern, replacement, content)
|
||||
|
||||
# Manual replacement for multi-line status
|
||||
content = content.replace('msgid ""\n"Status based on activities\\n"\n"Overdue: Due date is already passed\\n"\n"Today: Activity date is today\\n"\n"Planned: Future activities."\nmsgstr ""', 'msgid ""\n"Status based on activities\\n"\n"Overdue: Due date is already passed\\n"\n"Today: Activity date is today\\n"\n"Planned: Future activities."\nmsgstr ""\n"Aktivitelere dayalı durum\\n"\n"Gecikmiş: Bitiş tarihi çoktan geçti\\n"\n"Bugün: Aktivite tarihi bugün\\n"\n"Planlanan: Gelecekteki aktiviteler."')
|
||||
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
@@ -1,4 +1,11 @@
|
||||
from . import social_account
|
||||
from . import social_post
|
||||
from . import social_media
|
||||
from . import social_live_post
|
||||
from . import social_stream
|
||||
from . import social_stream_post
|
||||
from . import social_competitor
|
||||
from . import res_config_settings
|
||||
from . import res_company
|
||||
from . import social_media
|
||||
from . import social_auto_reply
|
||||
from . import social_auto_reply_log
|
||||
13
extra-addons/mymach_social_nextgen/models/res_company.py
Normal file
13
extra-addons/mymach_social_nextgen/models/res_company.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from odoo import models, fields
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
social_watermark_image = fields.Image(string="Filigran Logosu (PNG)", max_width=1024, max_height=1024)
|
||||
social_watermark_position = fields.Selection([
|
||||
('bottom_right', 'Sağ Alt'),
|
||||
('bottom_left', 'Sol Alt'),
|
||||
('top_right', 'Sağ Üst'),
|
||||
('top_left', 'Sol Üst'),
|
||||
('center', 'Merkez')
|
||||
], string="Filigran Konumu", default='bottom_right')
|
||||
@@ -0,0 +1,29 @@
|
||||
from odoo import models, fields
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
# Meta (Facebook/Instagram) API
|
||||
social_meta_client_id = fields.Char(string="Meta Client ID", config_parameter="mymach_social.meta_client_id")
|
||||
social_meta_client_secret = fields.Char(string="Meta Client Secret", config_parameter="mymach_social.meta_client_secret")
|
||||
|
||||
# LinkedIn API
|
||||
social_linkedin_client_id = fields.Char(string="LinkedIn Client ID", config_parameter="mymach_social.linkedin_client_id")
|
||||
social_linkedin_client_secret = fields.Char(string="LinkedIn Client Secret", config_parameter="mymach_social.linkedin_client_secret")
|
||||
|
||||
# X (Twitter) API
|
||||
social_x_client_id = fields.Char(string="X (Twitter) Client ID", config_parameter="mymach_social.x_client_id")
|
||||
social_x_client_secret = fields.Char(string="X (Twitter) Client Secret", config_parameter="mymach_social.x_client_secret")
|
||||
|
||||
# YouTube API
|
||||
social_youtube_client_id = fields.Char(string="YouTube Client ID", config_parameter="mymach_social.youtube_client_id")
|
||||
social_youtube_client_secret = fields.Char(string="YouTube Client Secret", config_parameter="mymach_social.youtube_client_secret")
|
||||
|
||||
# AI APIs
|
||||
social_gemini_api_key = fields.Char(string="Google Gemini API Key", config_parameter="mymach_social.gemini_api_key")
|
||||
social_openai_api_key = fields.Char(string="OpenAI API Key", config_parameter="mymach_social.openai_api_key")
|
||||
social_claude_api_key = fields.Char(string="Claude API Key", config_parameter="mymach_social.claude_api_key")
|
||||
social_grok_api_key = fields.Char(string="Grok API Key", config_parameter="mymach_social.grok_api_key")
|
||||
# Watermark Settings
|
||||
social_watermark_image = fields.Image(related='company_id.social_watermark_image', readonly=False)
|
||||
social_watermark_position = fields.Selection(related='company_id.social_watermark_position', readonly=False)
|
||||
@@ -1,31 +1,144 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
from werkzeug.urls import url_join
|
||||
|
||||
from ..api.meta_api import MetaAPI
|
||||
from ..api.instagram_api import InstagramAPI
|
||||
from ..api.linkedin_api import LinkedInAPI
|
||||
from ..api.x_api import XAPI
|
||||
from ..api.youtube_api import YouTubeAPI
|
||||
from ..api.tiktok_api import TikTokAPI
|
||||
|
||||
class SocialAccount(models.Model):
|
||||
_name = 'social.account'
|
||||
_name = 'mymach.social.account'
|
||||
_description = 'Social Media Account'
|
||||
|
||||
name = fields.Char(string='Account Name', required=True)
|
||||
name = fields.Char(string='Hesap Adı', required=True)
|
||||
platform = fields.Selection([
|
||||
('facebook', 'Facebook'),
|
||||
('instagram', 'Instagram'),
|
||||
('twitter', 'X (Twitter)'),
|
||||
('linkedin', 'LinkedIn'),
|
||||
('x', 'X (Twitter)'),
|
||||
('youtube', 'YouTube')
|
||||
('youtube', 'YouTube'),
|
||||
('tiktok', 'TikTok'),
|
||||
('web_push', 'Web Push Bildirimleri')
|
||||
], string='Platform', required=True)
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
access_token = fields.Char(string='Access Token', groups="base.group_system")
|
||||
refresh_token = fields.Char(string='Refresh Token', groups="base.group_system")
|
||||
token_expiration = fields.Datetime(string='Token Expiration Date')
|
||||
active = fields.Boolean(string='Aktif', default=True)
|
||||
access_token = fields.Char(string='Erişim Anahtarı (Access Token)', groups="base.group_system")
|
||||
refresh_token = fields.Char(string='Yenileme Anahtarı (Refresh Token)', groups="base.group_system")
|
||||
token_expiration = fields.Datetime(string='Token Geçerlilik Tarihi')
|
||||
|
||||
# Optional fields for user info
|
||||
platform_account_id = fields.Char(string='Platform Account ID')
|
||||
profile_image_url = fields.Char(string='Profile Image URL')
|
||||
platform_account_id = fields.Char(string='Platform Hesap ID')
|
||||
profile_image_url = fields.Char(string='Profil Resmi URL')
|
||||
|
||||
color = fields.Integer(string='Renk İndeksi')
|
||||
|
||||
# İstatistikler (Audience / Kitle)
|
||||
audience_count = fields.Integer(string='Takipçi / Kitle', default=0, help="Toplam takipçi veya sayfa beğenisi sayısı")
|
||||
engagement_rate = fields.Float(string='Etkileşim Oranı (%)', default=0.0)
|
||||
|
||||
def _get_api_client(self):
|
||||
self.ensure_one()
|
||||
config = self.env['ir.config_parameter'].sudo()
|
||||
base_url = config.get_param('web.base.url')
|
||||
redirect_uri = url_join(base_url, f'/api/social/callback/{self.platform}')
|
||||
|
||||
if self.platform == 'facebook':
|
||||
client_id = config.get_param('mymach_social.meta_client_id')
|
||||
client_secret = config.get_param('mymach_social.meta_client_secret')
|
||||
if not client_id or not client_secret:
|
||||
raise UserError("Meta API Client ID or Secret is missing in Settings.")
|
||||
return MetaAPI(client_id, client_secret, redirect_uri)
|
||||
|
||||
elif self.platform == 'instagram':
|
||||
client_id = config.get_param('mymach_social.meta_client_id')
|
||||
client_secret = config.get_param('mymach_social.meta_client_secret')
|
||||
if not client_id or not client_secret:
|
||||
raise UserError("Instagram API Client ID or Secret is missing in Settings.")
|
||||
return InstagramAPI(client_id, client_secret, redirect_uri)
|
||||
|
||||
elif self.platform == 'linkedin':
|
||||
client_id = config.get_param('mymach_social.linkedin_client_id')
|
||||
client_secret = config.get_param('mymach_social.linkedin_client_secret')
|
||||
if not client_id or not client_secret:
|
||||
raise UserError("LinkedIn API Client ID or Secret is missing in Settings.")
|
||||
return LinkedInAPI(client_id, client_secret, redirect_uri)
|
||||
|
||||
elif self.platform == 'x':
|
||||
client_id = config.get_param('mymach_social.x_client_id')
|
||||
client_secret = config.get_param('mymach_social.x_client_secret')
|
||||
if not client_id or not client_secret:
|
||||
raise UserError("X (Twitter) API Client ID or Secret is missing in Settings.")
|
||||
return XAPI(client_id, client_secret, redirect_uri)
|
||||
|
||||
elif self.platform == 'youtube':
|
||||
client_id = config.get_param('mymach_social.youtube_client_id')
|
||||
client_secret = config.get_param('mymach_social.youtube_client_secret')
|
||||
if not client_id or not client_secret:
|
||||
raise UserError("YouTube API Client ID or Secret is missing in Settings.")
|
||||
return YouTubeAPI(client_id, client_secret, redirect_uri)
|
||||
|
||||
elif self.platform == 'tiktok':
|
||||
client_id = config.get_param('mymach_social.tiktok_client_id')
|
||||
client_secret = config.get_param('mymach_social.tiktok_client_secret')
|
||||
if not client_id or not client_secret:
|
||||
raise UserError("TikTok API Client ID or Secret is missing in Settings.")
|
||||
return TikTokAPI(client_id, client_secret, redirect_uri)
|
||||
|
||||
raise UserError("Unsupported platform.")
|
||||
|
||||
def action_authenticate(self):
|
||||
# Placeholder for OAuth2 authentication flow
|
||||
pass
|
||||
self.ensure_one()
|
||||
api_client = self._get_api_client()
|
||||
auth_url = api_client.get_auth_url(state=str(self.id))
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': auth_url,
|
||||
'target': 'self'
|
||||
}
|
||||
|
||||
def process_oauth_callback(self, code):
|
||||
self.ensure_one()
|
||||
api_client = self._get_api_client()
|
||||
token_data = api_client.exchange_code_for_token(code)
|
||||
|
||||
# Save tokens
|
||||
self.access_token = token_data.get('access_token')
|
||||
if 'refresh_token' in token_data:
|
||||
self.refresh_token = token_data.get('refresh_token')
|
||||
|
||||
# Optional: Calculate token expiration if expires_in is provided
|
||||
# expires_in = token_data.get('expires_in')
|
||||
|
||||
# In a real app, we would also fetch user profile data here.
|
||||
|
||||
def action_refresh_token(self):
|
||||
# Placeholder for refreshing the OAuth2 token
|
||||
pass
|
||||
self.ensure_one()
|
||||
if self.platform == 'facebook' and self.access_token:
|
||||
try:
|
||||
api_client = self._get_api_client()
|
||||
resp = api_client.refresh_token(self.access_token)
|
||||
new_token = resp.get('access_token')
|
||||
if new_token:
|
||||
self.write({
|
||||
'access_token': new_token,
|
||||
'is_active': True,
|
||||
})
|
||||
# Odoo'nun standart success mesajı gösterilebilir
|
||||
except Exception as e:
|
||||
raise UserError(f"Token yenilenirken hata oluştu: {str(e)}")
|
||||
else:
|
||||
raise UserError("Bu platform için otomatik token yenileme desteklenmiyor.")
|
||||
|
||||
@api.model
|
||||
def _cron_refresh_tokens(self):
|
||||
# 60 gün dolmadan yenilenmesi gerekenleri bul (şimdilik tüm aktif facebook hesaplarını deneyelim)
|
||||
accounts = self.search([('is_active', '=', True), ('platform', '=', 'facebook')])
|
||||
for acc in accounts:
|
||||
try:
|
||||
acc.action_refresh_token()
|
||||
except Exception as e:
|
||||
# Log error and continue with others
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class SocialAutoReplyRule(models.Model):
|
||||
_name = 'mymach.social.auto.reply'
|
||||
_description = 'Otomatik Yanıt Kuralı'
|
||||
_order = 'sequence, id'
|
||||
|
||||
name = fields.Char(string='Kural Adı', required=True)
|
||||
active = fields.Boolean(string='Aktif', default=True)
|
||||
sequence = fields.Integer(string='Öncelik', default=10, help="Düşük sayı daha önce çalışır.")
|
||||
|
||||
keyword = fields.Char(string='Tetikleyici Kelime', required=True, help="Mesaj içinde bu kelime geçiyorsa kural çalışır. Küçük/büyük harf duyarlı değildir.")
|
||||
reply_message = fields.Text(string='Otomatik Yanıt Metni', required=True)
|
||||
|
||||
apply_to_message_type = fields.Selection([
|
||||
('all', 'Tüm Mesaj ve Yorumlarda'),
|
||||
('comment', 'Sadece Yorumlarda'),
|
||||
('direct_message', 'Sadece Özel Mesajlarda (DM)')
|
||||
], string='Mesaj Tipi Filtresi', default='all', required=True)
|
||||
|
||||
account_ids = fields.Many2many(
|
||||
'mymach.social.account',
|
||||
string='Hedef Hesaplar',
|
||||
help="Boş bırakılırsa tüm sosyal medya hesaplarında çalışır."
|
||||
)
|
||||
|
||||
log_ids = fields.One2many('mymach.social.auto.reply.log', 'rule_id', string='İşlem Geçmişi')
|
||||
log_count = fields.Integer(string='Log Sayısı', compute='_compute_log_count')
|
||||
|
||||
@api.depends('log_ids')
|
||||
def _compute_log_count(self):
|
||||
for rule in self:
|
||||
rule.log_count = len(rule.log_ids)
|
||||
|
||||
def action_view_logs(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': 'İşlem Geçmişi',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'mymach.social.auto.reply.log',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('rule_id', '=', self.id)],
|
||||
'context': {'default_rule_id': self.id},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
from odoo import models, fields
|
||||
|
||||
class SocialAutoReplyLog(models.Model):
|
||||
_name = 'mymach.social.auto.reply.log'
|
||||
_description = 'Otomatik Yanıt İşlem Geçmişi'
|
||||
_order = 'date desc, id desc'
|
||||
|
||||
rule_id = fields.Many2one('mymach.social.auto.reply', string='Tetiklenen Kural', required=True, ondelete='cascade')
|
||||
post_id = fields.Many2one('mymach.social.stream.post', string='İlgili Mesaj/Yorum', ondelete='set null')
|
||||
account_id = fields.Many2one('mymach.social.account', string='Sosyal Hesap')
|
||||
|
||||
author_name = fields.Char(string='Yazar / Kullanıcı')
|
||||
trigger_keyword = fields.Char(string='Tetikleyici Kelime')
|
||||
sent_message = fields.Text(string='Gönderilen Yanıt')
|
||||
|
||||
status = fields.Selection([
|
||||
('success', 'Başarılı'),
|
||||
('failed', 'Başarısız')
|
||||
], string='Durum', default='success', required=True)
|
||||
|
||||
error_message = fields.Text(string='Hata Mesajı')
|
||||
date = fields.Datetime(string='İşlem Tarihi', default=fields.Datetime.now, required=True)
|
||||
@@ -22,5 +22,19 @@ class SocialCompetitor(models.Model):
|
||||
notes = fields.Text(string='Analysis Notes')
|
||||
|
||||
def action_scrape_data(self):
|
||||
# Placeholder for BeautifulSoup / Scrapy scraping logic
|
||||
pass
|
||||
import random
|
||||
for record in self:
|
||||
# Mock scraping logic. In a real module, use requests + bs4 or official API
|
||||
new_followers = record.followers_count + random.randint(10, 500) if record.followers_count else random.randint(5000, 100000)
|
||||
new_engagement = round(random.uniform(1.5, 8.5), 2)
|
||||
|
||||
record.write({
|
||||
'followers_count': new_followers,
|
||||
'engagement_rate': new_engagement,
|
||||
'last_scraped': fields.Datetime.now()
|
||||
})
|
||||
|
||||
@api.model
|
||||
def _cron_scrape_competitors(self):
|
||||
competitors = self.search([])
|
||||
competitors.action_scrape_data()
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class SocialLivePost(models.Model):
|
||||
_name = 'mymach.social.live.post'
|
||||
_description = 'Social Live Post'
|
||||
_rec_name = 'account_id'
|
||||
|
||||
post_id = fields.Many2one('mymach.social.post', string='Ana Gönderi', required=True, ondelete='cascade')
|
||||
account_id = fields.Many2one('mymach.social.account', string='Hesap', required=True, ondelete='cascade')
|
||||
state = fields.Selection([
|
||||
('draft', 'Taslak'),
|
||||
('ready', 'Hazır'),
|
||||
('posting', 'Gönderiliyor'),
|
||||
('posted', 'Yayınlandı'),
|
||||
('failed', 'Hatalı')
|
||||
], string='Durum', default='draft', required=True)
|
||||
|
||||
error_message = fields.Text('Hata Mesajı')
|
||||
|
||||
# Platform-specific IDs
|
||||
platform_post_id = fields.Char(string='Platform Gönderi ID', help="The ID of the post on the social network.")
|
||||
|
||||
# Engagement stats for this specific account
|
||||
likes_count = fields.Integer(string='Beğeniler', default=0)
|
||||
comments_count = fields.Integer(string='Yorumlar', default=0)
|
||||
shares_count = fields.Integer(string='Paylaşımlar', default=0)
|
||||
clicks_count = fields.Integer(string='Tıklamalar', default=0)
|
||||
@@ -1,27 +1,25 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo import models, fields
|
||||
|
||||
class SocialMedia(models.Model):
|
||||
_name = 'social.media'
|
||||
_description = 'Social Media Library'
|
||||
_name = 'mymach.social.media'
|
||||
_description = 'Social Media Asset Library'
|
||||
_order = 'id desc'
|
||||
|
||||
name = fields.Char(string='File Name', required=True)
|
||||
name = fields.Char(string='Medya Adı', required=True)
|
||||
media_type = fields.Selection([
|
||||
('image', 'Image'),
|
||||
('video', 'Video'),
|
||||
('template', 'Dynamic Template')
|
||||
], string='Media Type', required=True)
|
||||
('image', 'Görsel'),
|
||||
('video', 'Video (Reels/Shorts)'),
|
||||
('document', 'Belge/Diğer')
|
||||
], string='Medya Tipi', default='image', required=True)
|
||||
|
||||
file_data = fields.Binary(string='File', required=True)
|
||||
mimetype = fields.Char(string='Mime Type')
|
||||
attachment_id = fields.Many2one('ir.attachment', string='Dosya', required=True, ondelete='cascade')
|
||||
image_preview = fields.Binary(related='attachment_id.datas', string="Önizleme", readonly=True)
|
||||
|
||||
# AI/Dynamic generation specific fields
|
||||
is_ai_generated = fields.Boolean(string='AI Generated', default=False)
|
||||
template_id = fields.Many2one('social.media', string='Source Template', domain=[('media_type', '=', 'template')])
|
||||
|
||||
def action_generate_watermark(self):
|
||||
# Placeholder for PIL watermark logic
|
||||
pass
|
||||
|
||||
def action_generate_video(self):
|
||||
# Placeholder for MoviePy video generation
|
||||
pass
|
||||
tag_ids = fields.Many2many('mymach.social.media.tag', string='Etiketler')
|
||||
|
||||
class SocialMediaTag(models.Model):
|
||||
_name = 'mymach.social.media.tag'
|
||||
_description = 'Media Tag'
|
||||
|
||||
name = fields.Char(string='Etiket', required=True)
|
||||
color = fields.Integer(string='Renk İndeksi')
|
||||
|
||||
@@ -1,42 +1,297 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
import random
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class SocialPost(models.Model):
|
||||
_name = 'social.post'
|
||||
_name = 'mymach.social.post'
|
||||
_description = 'Social Media Post'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'scheduled_date desc, id desc'
|
||||
|
||||
name = fields.Char(string='Post Title', required=True, tracking=True)
|
||||
content = fields.Text(string='Content', tracking=True)
|
||||
name = fields.Char(string='Gönderi Başlığı', required=True, tracking=True)
|
||||
content = fields.Text(string='İçerik', required=True, tracking=True)
|
||||
|
||||
account_ids = fields.Many2many(
|
||||
'mymach.social.account',
|
||||
string='Sosyal Medya Hesapları',
|
||||
required=True
|
||||
)
|
||||
|
||||
post_type = fields.Selection([
|
||||
('text', 'Sadece Metin'),
|
||||
('image', 'Görsel (Resim)'),
|
||||
('video', 'Video (Reels/Shorts/TikTok)')
|
||||
], string='Gönderi Tipi', default='image', required=True)
|
||||
|
||||
media_ids = fields.Many2many('ir.attachment', string='Medyalar (Görseller)')
|
||||
utm_campaign_id = fields.Many2one('utm.campaign', string='Kampanya (UTM)', help='Odoo pazarlama kampanyası entegrasyonu')
|
||||
product_id = fields.Many2one('product.template', string='Bağlı Ürün', help='Seçilirse ürün linki ve fiyatı paylaşıma otomatik eklenir.')
|
||||
|
||||
scheduled_date = fields.Datetime(string='Planlanan Tarih', tracking=True)
|
||||
published_date = fields.Datetime(string='Yayınlanma Tarihi', readonly=True, tracking=True)
|
||||
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('approval', 'Waiting for Approval'),
|
||||
('scheduled', 'Scheduled'),
|
||||
('published', 'Published'),
|
||||
('failed', 'Failed')
|
||||
], string='Status', default='draft', tracking=True)
|
||||
('draft', 'Taslak'),
|
||||
('waiting', 'Onay Bekliyor'),
|
||||
('scheduled', 'Planlandı'),
|
||||
('published', 'Yayınlandı'),
|
||||
('failed', 'Hatalı')
|
||||
], string='Durum', default='draft', tracking=True)
|
||||
|
||||
scheduled_date = fields.Datetime(string='Scheduled Date', tracking=True)
|
||||
published_date = fields.Datetime(string='Published Date', readonly=True)
|
||||
|
||||
account_ids = fields.Many2many('social.account', string='Social Accounts', required=True)
|
||||
media_ids = fields.Many2many('social.media', string='Media Files')
|
||||
|
||||
error_message = fields.Text(string='Error Message', readonly=True)
|
||||
error_message = fields.Text(string='Hata Mesajı', readonly=True)
|
||||
color = fields.Integer(string='Renk İndeksi')
|
||||
|
||||
def action_submit_for_approval(self):
|
||||
for record in self:
|
||||
record.state = 'approval'
|
||||
# Analytics Metrics
|
||||
likes_count = fields.Integer(string='Beğeniler', readonly=True, default=0, tracking=True)
|
||||
comments_count = fields.Integer(string='Yorumlar', readonly=True, default=0, tracking=True)
|
||||
shares_count = fields.Integer(string='Paylaşımlar', readonly=True, default=0, tracking=True)
|
||||
|
||||
click_count = fields.Integer(string='Tıklamalar / Ziyaretçiler', compute='_compute_click_count')
|
||||
|
||||
def _compute_click_count(self):
|
||||
for post in self:
|
||||
if not post.utm_campaign_id:
|
||||
post.click_count = 0
|
||||
continue
|
||||
# Gelişmiş link.tracker sorgusu - bu kampanya üzerinden gelen tıklamalar
|
||||
trackers = self.env['link.tracker'].search([('campaign_id', '=', post.utm_campaign_id.id)])
|
||||
post.click_count = sum(trackers.mapped('count'))
|
||||
|
||||
def action_approve(self):
|
||||
for record in self:
|
||||
if record.scheduled_date:
|
||||
record.state = 'scheduled'
|
||||
else:
|
||||
record.action_publish()
|
||||
live_post_ids = fields.One2many('mymach.social.live.post', 'post_id', string='Canlı Gönderiler', readonly=True)
|
||||
|
||||
# 1. Gönderi Önizleme (Live Preview)
|
||||
preview_html = fields.Html(string="Önizleme", compute="_compute_preview_html")
|
||||
|
||||
# 5. Platform Validasyonu (Constraint)
|
||||
@api.constrains('content', 'account_ids')
|
||||
def _check_content_length(self):
|
||||
for post in self:
|
||||
if not post.content:
|
||||
continue
|
||||
for account in post.account_ids:
|
||||
if account.platform == 'twitter' and len(post.content) > 280:
|
||||
raise UserError(f"X (Twitter) için karakter sınırı 280'dir. Şu an {len(post.content)} karakter yazdınız.")
|
||||
elif account.platform == 'linkedin' and len(post.content) > 3000:
|
||||
raise UserError(f"LinkedIn için karakter sınırı 3000'dir. Şu an {len(post.content)} karakter yazdınız.")
|
||||
|
||||
@api.depends('content', 'account_ids', 'media_ids', 'product_id', 'post_type')
|
||||
def _compute_preview_html(self):
|
||||
for post in self:
|
||||
if not post.content and not post.media_ids:
|
||||
post.preview_html = "<div class='text-muted text-center p-5'>Gönderi önizlemesi için içerik veya medya ekleyin...</div>"
|
||||
continue
|
||||
|
||||
# Mobil Mock-up tasarımı (iPhone Tarzı)
|
||||
preview_content = post.content or ""
|
||||
if post.product_id:
|
||||
price = post.product_id.list_price
|
||||
link = f"https://mymach.com/shop/product/{post.product_id.id}"
|
||||
preview_content += f"\n\nSatın Al: {post.product_id.name} - {price} ₺\n{link}"
|
||||
|
||||
media_label = "Medyalar Yüklenecek"
|
||||
if post.post_type == 'video':
|
||||
media_label = f"▶ Video Önizlemesi ({len(post.media_ids)} Dosya)"
|
||||
elif post.post_type == 'image':
|
||||
media_label = f"📷 Görsel Alanı ({len(post.media_ids)} Dosya)"
|
||||
|
||||
preview = f"""
|
||||
<div style="margin: 0 auto; width: 320px; height: 600px; border: 12px solid #333; border-radius: 36px; position: relative; background: #fafafa; overflow: hidden; box-shadow: 0 10px 25px rgba(0,0,0,0.1);">
|
||||
<!-- Notch -->
|
||||
<div style="position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 120px; height: 25px; background: #333; border-bottom-left-radius: 15px; border-bottom-right-radius: 15px; z-index: 10;"></div>
|
||||
|
||||
<!-- App Header -->
|
||||
<div style="padding: 35px 15px 10px; background: #fff; border-bottom: 1px solid #ddd; display: flex; align-items: center; justify-content: space-between;">
|
||||
<div style="font-weight: bold; font-family: sans-serif; font-size: 18px;">SocialApp</div>
|
||||
</div>
|
||||
|
||||
<!-- Post Area -->
|
||||
<div style="background: #fff; margin-top: 10px; padding: 15px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
|
||||
<!-- User Info -->
|
||||
<div style="display: flex; align-items: center; margin-bottom: 12px;">
|
||||
<div style="width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(45deg, #f09433 0%, #e6683c 25%, #dc2743 50%, #cc2366 75%, #bc1888 100%); display: flex; align-items: center; justify-content: center; margin-right: 10px;">
|
||||
<div style="width: 32px; height: 32px; border-radius: 50%; background: #fff; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px; color: #333;">MM</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight: 600; font-size: 13px; color: #262626;">MyMach Official</div>
|
||||
<div style="color: #8e8e8e; font-size: 11px;">Sponsored</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Media Placeholder -->
|
||||
<div style="width: 100%; height: 250px; background: #efefef; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: #8e8e8e; font-size: 14px; margin-bottom: 12px;">
|
||||
{media_label}
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="display: flex; gap: 12px; margin-bottom: 10px; color: #262626; font-size: 18px;">
|
||||
♡ 💬 ➦
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div style="font-size: 13px; color: #262626; white-space: pre-wrap; line-height: 1.4;">
|
||||
<span style="font-weight: 600;">MyMach Official</span> {preview_content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
post.preview_html = preview
|
||||
|
||||
# 4. Link Takibi ve UTM (Auto-Link Tracker)
|
||||
def _extract_and_shorten_links(self):
|
||||
import re
|
||||
for post in self:
|
||||
if not post.content:
|
||||
continue
|
||||
# Temel URL regex'i
|
||||
urls = re.findall(r'(https?://[^\s]+)', post.content)
|
||||
new_content = post.content
|
||||
for url in urls:
|
||||
# Odoo'nun link.tracker modelini kullanarak UTM'li kısa link yarat
|
||||
tracker = self.env['link.tracker'].create({
|
||||
'url': url,
|
||||
'campaign_id': post.utm_campaign_id.id if post.utm_campaign_id else False,
|
||||
'title': f"{post.name} Linki"
|
||||
})
|
||||
# Kısa linkle orijinali değiştir
|
||||
new_content = new_content.replace(url, tracker.short_url)
|
||||
|
||||
if new_content != post.content:
|
||||
post.content = new_content
|
||||
|
||||
def action_view_visitors(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': 'Bağlantı İzleyici (Ziyaretçiler)',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'link.tracker',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('campaign_id', '=', self.utm_campaign_id.id)]
|
||||
}
|
||||
|
||||
def action_open_ai_wizard(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': 'AI ile İçerik Üret',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'mymach.social.ai.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {'default_post_id': self.id}
|
||||
}
|
||||
|
||||
def action_open_ai_image_wizard(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': 'AI ile Görsel Üret',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'mymach.social.ai.image.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {'default_post_id': self.id}
|
||||
}
|
||||
|
||||
def action_request_approval(self):
|
||||
self.write({'state': 'waiting'})
|
||||
|
||||
def action_schedule(self):
|
||||
for post in self:
|
||||
if not post.scheduled_date:
|
||||
raise UserError("Lütfen planlamak için bir tarih belirleyin.")
|
||||
if post.scheduled_date < fields.Datetime.now():
|
||||
raise UserError("Planlanmış tarih geçmişte olamaz.")
|
||||
self.write({'state': 'scheduled'})
|
||||
|
||||
def action_publish_now(self):
|
||||
self.write({'scheduled_date': fields.Datetime.now()})
|
||||
self.action_publish()
|
||||
|
||||
def action_publish(self):
|
||||
for record in self:
|
||||
# Placeholder for publishing logic
|
||||
record.state = 'published'
|
||||
record.published_date = fields.Datetime.now()
|
||||
# Eğer ürün seçiliyse metne ekle
|
||||
for post in self:
|
||||
if post.product_id and post.content:
|
||||
price = post.product_id.list_price
|
||||
|
||||
# UTM Etiketleri Ekle (Analytics İzleme)
|
||||
campaign_name = post.utm_campaign_id.name if post.utm_campaign_id else "social_post"
|
||||
campaign_name = campaign_name.replace(' ', '_').lower()
|
||||
link = f"https://mymach.com/shop/product/{post.product_id.id}?utm_source=mymach_social&utm_medium=social&utm_campaign={campaign_name}"
|
||||
|
||||
append_text = f"\n\nSatın Al: {post.product_id.name} - {price} ₺\n{link}"
|
||||
if append_text not in post.content:
|
||||
post.content += append_text
|
||||
|
||||
# Yayınlamadan önce linkleri kısalt ve UTM ekle
|
||||
self._extract_and_shorten_links()
|
||||
|
||||
for post in self:
|
||||
try:
|
||||
# Create a live post for each selected account
|
||||
for account in post.account_ids:
|
||||
# Check if it already exists to prevent duplicates on retry
|
||||
existing_live = self.env['mymach.social.live.post'].search([
|
||||
('post_id', '=', post.id),
|
||||
('account_id', '=', account.id)
|
||||
], limit=1)
|
||||
|
||||
if not existing_live:
|
||||
live_post = self.env['mymach.social.live.post'].create({
|
||||
'post_id': post.id,
|
||||
'account_id': account.id,
|
||||
'state': 'ready'
|
||||
})
|
||||
api_client = None
|
||||
try:
|
||||
api_client = account._get_api_client()
|
||||
except Exception as e:
|
||||
_logger.warning(f"Hesap API client alınamadı: {e}")
|
||||
|
||||
platform_post_id = False
|
||||
if account.platform == 'facebook' and account.access_token and api_client:
|
||||
page_id = account.platform_account_id or 'me'
|
||||
# API İsteği
|
||||
resp = api_client.publish_post(page_id, account.access_token, post.content)
|
||||
platform_post_id = resp.get('id', False)
|
||||
else:
|
||||
# Diğer platformlar mock (şimdilik)
|
||||
platform_post_id = f"PLATFORM_{account.id}_{post.id}"
|
||||
|
||||
live_post.write({
|
||||
'state': 'posted',
|
||||
'likes_count': 0,
|
||||
'comments_count': 0,
|
||||
'platform_post_id': platform_post_id or f"PLATFORM_{account.id}_{post.id}"
|
||||
})
|
||||
|
||||
# Overall post metrics can be aggregated or simulated
|
||||
post.write({
|
||||
'state': 'published',
|
||||
'published_date': fields.Datetime.now(),
|
||||
'error_message': False,
|
||||
'likes_count': sum(post.live_post_ids.mapped('likes_count')),
|
||||
'comments_count': sum(post.live_post_ids.mapped('comments_count')),
|
||||
'shares_count': sum(post.live_post_ids.mapped('shares_count')),
|
||||
})
|
||||
except Exception as e:
|
||||
post.write({
|
||||
'state': 'failed',
|
||||
'error_message': f"Yayınlama Hatası: {str(e)}"
|
||||
})
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
self.write({'state': 'draft', 'error_message': False})
|
||||
|
||||
@api.model
|
||||
def _cron_publish_scheduled_posts(self):
|
||||
_logger.info("Cron: Zamanlanmış gönderiler kontrol ediliyor...")
|
||||
posts_to_publish = self.search([
|
||||
('state', '=', 'scheduled'),
|
||||
('scheduled_date', '<=', fields.Datetime.now())
|
||||
])
|
||||
if posts_to_publish:
|
||||
_logger.info(f"Cron: Yayınlanacak {len(posts_to_publish)} gönderi bulundu.")
|
||||
posts_to_publish.action_publish()
|
||||
|
||||
17
extra-addons/mymach_social_nextgen/models/social_stream.py
Normal file
17
extra-addons/mymach_social_nextgen/models/social_stream.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class SocialStream(models.Model):
|
||||
_name = 'mymach.social.stream'
|
||||
_description = 'Social Media Stream'
|
||||
|
||||
name = fields.Char(string='Akış Adı', required=True)
|
||||
account_id = fields.Many2one('mymach.social.account', string='Hesap', required=True, ondelete='cascade')
|
||||
stream_post_ids = fields.One2many('mymach.social.stream.post', 'stream_id', string='Akış Gönderileri')
|
||||
stream_type = fields.Selection([
|
||||
('feed', 'Zaman Tüneli / Feed'),
|
||||
('mentions', 'Bahsetmeler'),
|
||||
('messages', 'Mesajlar'),
|
||||
('keyword', 'Anahtar Kelime (Dinleme)')
|
||||
], string='Akış Tipi', default='feed', required=True)
|
||||
|
||||
search_keyword = fields.Char(string='Takip Edilecek Kelime/Hashtag', help='Sadece Akış Tipi "Anahtar Kelime" ise kullanılır. Örn: #teknoloji')
|
||||
140
extra-addons/mymach_social_nextgen/models/social_stream_post.py
Normal file
140
extra-addons/mymach_social_nextgen/models/social_stream_post.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class SocialStreamPost(models.Model):
|
||||
_name = 'mymach.social.stream.post'
|
||||
_inherit = ['mail.thread']
|
||||
_description = 'Social Stream Post'
|
||||
_order = 'published_date desc'
|
||||
|
||||
stream_id = fields.Many2one('mymach.social.stream', string='Akış', required=True, ondelete='cascade')
|
||||
account_id = fields.Many2one(related='stream_id.account_id', string='Hesap', store=True)
|
||||
author_name = fields.Char(string='Yazar')
|
||||
author_link = fields.Char(string='Yazar Profil Linki')
|
||||
|
||||
published_date = fields.Datetime(string='Yayın Tarihi')
|
||||
message = fields.Text(string='Mesaj / İçerik')
|
||||
link_url = fields.Char(string='Gönderi Linki')
|
||||
|
||||
likes_count = fields.Integer(string='Beğeniler')
|
||||
comments_count = fields.Integer(string='Yorumlar')
|
||||
shares_count = fields.Integer(string='Paylaşımlar')
|
||||
|
||||
platform_post_id = fields.Char(string='Platform ID')
|
||||
|
||||
sentiment = fields.Selection([
|
||||
('positive', '😊 Olumlu'),
|
||||
('neutral', '😐 Nötr'),
|
||||
('negative', '😡 Olumsuz')
|
||||
], string='Duygu Analizi', default='neutral')
|
||||
|
||||
message_type = fields.Selection([
|
||||
('comment', 'Yorum'),
|
||||
('direct_message', 'Özel Mesaj (DM)')
|
||||
], string='Mesaj Tipi', default='comment', required=True)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
records = super().create(vals_list)
|
||||
for record in records:
|
||||
if not record.message:
|
||||
continue
|
||||
|
||||
# Otomatik Yanıt Kurallarını Kontrol Et
|
||||
rules = self.env['mymach.social.auto.reply'].search([('active', '=', True)], order='sequence, id')
|
||||
for rule in rules:
|
||||
# Hesap Filtresi Kontrolü
|
||||
if rule.account_ids and record.account_id not in rule.account_ids:
|
||||
continue
|
||||
|
||||
# Mesaj Tipi Filtresi Kontrolü
|
||||
if rule.apply_to_message_type != 'all' and rule.apply_to_message_type != record.message_type:
|
||||
continue
|
||||
|
||||
# Kelime Kontrolü
|
||||
if rule.keyword.lower() in record.message.lower():
|
||||
# Yanıtı gönder
|
||||
record.message_post(body=f"🤖 <b>Otomatik Yanıt Gönderildi:</b><br/>'{rule.keyword}' kelimesi algılandı.<br/><b>Yanıt:</b> {rule.reply_message}")
|
||||
|
||||
# Log kaydını oluştur
|
||||
self.env['mymach.social.auto.reply.log'].create({
|
||||
'rule_id': rule.id,
|
||||
'post_id': record.id,
|
||||
'account_id': record.account_id.id,
|
||||
'author_name': record.author_name,
|
||||
'trigger_keyword': rule.keyword,
|
||||
'sent_message': rule.reply_message,
|
||||
'status': 'success'
|
||||
})
|
||||
break # İlk eşleşen kural çalışsın ve döngü bitsin.
|
||||
|
||||
return records
|
||||
|
||||
def action_analyze_sentiment(self):
|
||||
# API anahtarını al
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.gemini_api_key')
|
||||
|
||||
for post in self:
|
||||
if not post.message:
|
||||
continue
|
||||
|
||||
if api_key:
|
||||
# Gerçek AI Analizi (Gemini)
|
||||
try:
|
||||
import requests
|
||||
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
|
||||
system_prompt = "Sen bir duygu analizi uzmanısın. Verilen metnin duygusunu analiz et ve sadece şu üç kelimeden birini döndür: 'positive', 'neutral', 'negative'."
|
||||
payload = {
|
||||
"system_instruction": {"parts": [{"text": system_prompt}]},
|
||||
"contents": [{"parts": [{"text": f"Metin: {post.message}"}]}],
|
||||
"generationConfig": {"temperature": 0.1, "maxOutputTokens": 10}
|
||||
}
|
||||
response = requests.post(url, headers={'Content-Type': 'application/json'}, json=payload)
|
||||
if response.status_code == 200:
|
||||
result = response.json().get('candidates', [{}])[0].get('content', {}).get('parts', [{}])[0].get('text', '').strip().lower()
|
||||
if 'positive' in result:
|
||||
post.sentiment = 'positive'
|
||||
elif 'negative' in result:
|
||||
post.sentiment = 'negative'
|
||||
else:
|
||||
post.sentiment = 'neutral'
|
||||
continue
|
||||
except Exception as e:
|
||||
# Log error, fallback to basic logic
|
||||
pass
|
||||
|
||||
# Fallback (API Key yoksa veya hata olursa) Basit kelime analizi
|
||||
lower_msg = post.message.lower()
|
||||
if any(word in lower_msg for word in ['harika', 'süper', 'teşekkür', 'iyi', 'başarılı']):
|
||||
post.sentiment = 'positive'
|
||||
elif any(word in lower_msg for word in ['kötü', 'rezalet', 'şikayet', 'sorun', 'hata', 'berbat']):
|
||||
post.sentiment = 'negative'
|
||||
else:
|
||||
post.sentiment = 'neutral'
|
||||
|
||||
def action_suggest_reply(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'name': 'AI ile Yanıt Öner',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'mymach.social.ai.reply.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {'default_stream_post_id': self.id}
|
||||
}
|
||||
|
||||
def action_create_lead(self):
|
||||
self.ensure_one()
|
||||
# Odoo CRM'de yeni bir Lead (Fırsat/Müşteri Adayı) yarat
|
||||
lead = self.env['crm.lead'].create({
|
||||
'name': f"Sosyal Medya Fırsatı: {self.author_name}",
|
||||
'description': f"İçerik: {self.message}\nLink: {self.link_url}",
|
||||
'type': 'opportunity'
|
||||
})
|
||||
return {
|
||||
'name': 'Yeni Müşteri Adayı',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'crm.lead',
|
||||
'res_id': lead.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current'
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_social_account_user,social.account.user,model_social_account,base.group_user,1,1,1,0
|
||||
access_social_account_manager,social.account.manager,model_social_account,base.group_system,1,1,1,1
|
||||
access_social_post_user,social.post.user,model_social_post,base.group_user,1,1,1,0
|
||||
access_social_post_manager,social.post.manager,model_social_post,base.group_system,1,1,1,1
|
||||
access_social_media_user,social.media.user,model_social_media,base.group_user,1,1,1,0
|
||||
access_social_media_manager,social.media.manager,model_social_media,base.group_system,1,1,1,1
|
||||
access_social_competitor_user,social.competitor.user,model_social_competitor,base.group_user,1,1,1,0
|
||||
access_social_competitor_manager,social.competitor.manager,model_social_competitor,base.group_system,1,1,1,1
|
||||
access_mymach_social_account_user,mymach.social.account.user,model_mymach_social_account,base.group_user,1,1,1,1
|
||||
access_mymach_social_post_user,mymach.social.post.user,model_mymach_social_post,base.group_user,1,1,1,1
|
||||
access_mymach_social_ai_wizard_user,mymach.social.ai.wizard.user,model_mymach_social_ai_wizard,base.group_user,1,1,1,1
|
||||
access_mymach_social_live_post_user,mymach.social.live.post.user,model_mymach_social_live_post,base.group_user,1,1,1,1
|
||||
access_mymach_social_stream_user,mymach.social.stream.user,model_mymach_social_stream,base.group_user,1,1,1,1
|
||||
access_mymach_social_stream_post_user,mymach.social.stream.post.user,model_mymach_social_stream_post,base.group_user,1,1,1,1
|
||||
access_mymach_social_competitor_user,social.competitor.user,model_social_competitor,base.group_user,1,1,1,1
|
||||
access_mymach_social_ai_image_wizard_user,mymach.social.ai.image.wizard.user,model_mymach_social_ai_image_wizard,base.group_user,1,1,1,1
|
||||
access_mymach_social_ai_reply_wizard_user,mymach.social.ai.reply.wizard.user,model_mymach_social_ai_reply_wizard,base.group_user,1,1,1,1
|
||||
access_social_media,access.mymach.social.media,model_mymach_social_media,base.group_user,1,1,1,1
|
||||
access_social_media_tag,access.mymach.social.media.tag,model_mymach_social_media_tag,base.group_user,1,1,1,1
|
||||
access_social_auto_reply,access.mymach.social.auto.reply,model_mymach_social_auto_reply,base.group_user,1,1,1,1
|
||||
access_social_auto_reply_log,access.mymach.social.auto.reply.log,model_mymach_social_auto_reply_log,base.group_user,1,1,1,1
|
||||
|
54
extra-addons/mymach_social_nextgen/security/ir_rule.xml
Normal file
54
extra-addons/mymach_social_nextgen/security/ir_rule.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!--
|
||||
Account Modeli İçin Kurallar
|
||||
-->
|
||||
<record id="rule_social_account_user" model="ir.rule">
|
||||
<field name="name">Social Account: User Rule</field>
|
||||
<field name="model_id" ref="model_mymach_social_account"/>
|
||||
<field name="domain_force">[('create_uid', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_social_account_manager" model="ir.rule">
|
||||
<field name="name">Social Account: Manager Rule</field>
|
||||
<field name="model_id" ref="model_mymach_social_account"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Post Modeli İçin Kurallar
|
||||
-->
|
||||
<record id="rule_social_post_user" model="ir.rule">
|
||||
<field name="name">Social Post: User Rule</field>
|
||||
<field name="model_id" ref="model_mymach_social_post"/>
|
||||
<field name="domain_force">[('create_uid', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_social_post_manager" model="ir.rule">
|
||||
<field name="name">Social Post: Manager Rule</field>
|
||||
<field name="model_id" ref="model_mymach_social_post"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
19
extra-addons/mymach_social_nextgen/security/security.xml
Normal file
19
extra-addons/mymach_social_nextgen/security/security.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- Sosyal Medya Uzmanı (Normal Kullanıcı) -->
|
||||
<record id="group_social_user" model="res.groups">
|
||||
<field name="name">Sosyal Medya Uzmanı</field>
|
||||
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="comment">Gönderi taslağı hazırlayabilir ve onay bekler statüsüne alabilir.</field>
|
||||
</record>
|
||||
|
||||
<!-- Sosyal Medya Yöneticisi (Manager) -->
|
||||
<record id="group_social_manager" model="res.groups">
|
||||
<field name="name">Sosyal Medya Yöneticisi</field>
|
||||
<field name="implied_ids" eval="[(4, ref('group_social_user'))]"/>
|
||||
<field name="comment">Taslakları onaylayabilir, doğrudan yayınlayabilir ve gelişmiş ayarları görebilir.</field>
|
||||
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,137 @@
|
||||
/* mymach_social_nextgen/static/src/css/social_dashboard.css */
|
||||
|
||||
/* 1. KANBAN VIEW (ANA ARKA PLAN) */
|
||||
.o_kanban_view.o_social_dashboard_kanban {
|
||||
background-color: #020617 !important; /* Çok daha koyu bir arka plan (Slate-950) */
|
||||
padding: 16px !important;
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
|
||||
/* 2. SÜTUNLAR (STREAMS) */
|
||||
.o_social_dashboard_kanban .o_kanban_group {
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
padding: 0 8px !important;
|
||||
}
|
||||
|
||||
/* Sütun Başlığı (Header) */
|
||||
.o_social_dashboard_kanban .o_kanban_header {
|
||||
background-color: #334155 !important; /* Arka plana göre çok daha açık bir renk (Slate-700) */
|
||||
border: 1px solid #475569 !important;
|
||||
border-radius: 12px 12px 0 0 !important;
|
||||
padding: 16px !important;
|
||||
border-bottom: none !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Sütun Başlığı Metni */
|
||||
.o_social_dashboard_kanban .o_kanban_header .o_column_title,
|
||||
.o_social_dashboard_kanban .o_kanban_header span,
|
||||
.o_social_dashboard_kanban .o_kanban_header div {
|
||||
color: #ffffff !important;
|
||||
font-weight: 700 !important;
|
||||
font-size: 16px !important;
|
||||
letter-spacing: 0.3px;
|
||||
text-shadow: 0 1px 2px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
/* Sütun İçeriği (Kartların dizildiği yer) */
|
||||
.o_social_dashboard_kanban .o_kanban_record_has_image_fill,
|
||||
.o_social_dashboard_kanban .o_kanban_group > .o_kanban_record {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* 3. KARTLAR (POSTS) */
|
||||
.o_social_dashboard_kanban .o_kanban_record {
|
||||
background-color: #1e293b !important; /* Başlıkla aynı uyumlu renk */
|
||||
border: 1px solid #334155 !important;
|
||||
border-radius: 12px !important;
|
||||
margin-top: 12px !important;
|
||||
margin-bottom: 12px !important;
|
||||
color: #e2e8f0 !important;
|
||||
padding: 16px !important;
|
||||
transition: all 0.2s ease-in-out;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Kart Hover Efekti */
|
||||
.o_social_dashboard_kanban .o_kanban_record:hover {
|
||||
border-color: #64748b !important;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Yazar Bilgisi Alanı */
|
||||
.o_social_author_section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Yazar Avatarı */
|
||||
.o_social_author_avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #3b82f6, #8b5cf6); /* Şık bir gradient */
|
||||
margin-right: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.o_social_author_name {
|
||||
font-weight: 600;
|
||||
color: #f1f5f9;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.o_social_time {
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Post İçeriği (Mesaj) */
|
||||
.o_social_message_text {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #cbd5e1;
|
||||
margin-bottom: 20px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
flex-grow: 1; /* Mesaj kısmının esnek olması için */
|
||||
}
|
||||
|
||||
/* Alt Aksiyon Butonları (Beğeni, Yorum, Paylaş) */
|
||||
.o_social_actions {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #334155;
|
||||
padding-top: 14px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.o_social_actions div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.o_social_actions div:hover {
|
||||
color: #38bdf8; /* Hover durumunda tatlı bir mavi */
|
||||
}
|
||||
|
||||
.o_social_actions i {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Odoo'nun Default Ekleme/Yükleme Butonlarını Gizle (Gerekirse) */
|
||||
.o_social_dashboard_kanban .o_kanban_group .o_kanban_header .o_kanban_config {
|
||||
color: #94a3b8 !important;
|
||||
}
|
||||
2
extra-addons/mymach_social_nextgen/tests/__init__.py
Normal file
2
extra-addons/mymach_social_nextgen/tests/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import test_social_core
|
||||
from . import test_social_auto_reply
|
||||
@@ -0,0 +1,85 @@
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
class TestSocialAutoReply(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSocialAutoReply, cls).setUpClass()
|
||||
# Test izolasyonu için mevcut tüm kuralları pasif yap
|
||||
cls.env['mymach.social.auto.reply'].search([]).write({'active': False})
|
||||
|
||||
# Testler için gerekli verileri oluştur
|
||||
cls.social_account = cls.env['mymach.social.account'].create({
|
||||
'name': 'Test Account',
|
||||
'platform': 'facebook',
|
||||
'active': True,
|
||||
})
|
||||
|
||||
cls.stream = cls.env['mymach.social.stream'].create({
|
||||
'name': 'Test Akışı',
|
||||
'account_id': cls.social_account.id,
|
||||
'stream_type': 'feed'
|
||||
})
|
||||
|
||||
def test_01_auto_reply_rule_creation(self):
|
||||
"""Otomatik yanıt kuralı oluşturulabildiğini test et."""
|
||||
rule = self.env['mymach.social.auto.reply'].create({
|
||||
'name': 'Fiyat Kuralı',
|
||||
'keyword': 'fiyat',
|
||||
'reply_message': 'Fiyatlarımız için sitemize bakınız.',
|
||||
'active': True
|
||||
})
|
||||
self.assertEqual(rule.keyword, 'fiyat')
|
||||
self.assertEqual(rule.log_count, 0)
|
||||
|
||||
def test_02_auto_reply_trigger_and_log(self):
|
||||
"""Bir gönderi oluşturulduğunda kuralın tetiklenip log oluşturduğunu test et."""
|
||||
# 1. Kuralı oluştur
|
||||
rule = self.env['mymach.social.auto.reply'].create({
|
||||
'name': 'Fiyat Kuralı',
|
||||
'keyword': 'fiyat',
|
||||
'reply_message': 'Fiyatlarımız web sitemizdedir.',
|
||||
'active': True
|
||||
})
|
||||
|
||||
# Başlangıçta log yok
|
||||
self.assertEqual(rule.log_count, 0)
|
||||
|
||||
# 2. 'fiyat' kelimesini içeren bir Stream Post oluştur
|
||||
# Oluşturma işlemi (create) sırasında `mymach.social.stream.post` kuralı kontrol edip log atacak
|
||||
post = self.env['mymach.social.stream.post'].create({
|
||||
'stream_id': self.stream.id,
|
||||
'author_name': 'Müşteri',
|
||||
'message': 'Merhaba, ürünün fiyat listesini alabilir miyim?',
|
||||
'message_type': 'comment'
|
||||
})
|
||||
|
||||
# 3. Logun oluştuğunu doğrula
|
||||
log = self.env['mymach.social.auto.reply.log'].search([('rule_id', '=', rule.id)])
|
||||
self.assertEqual(len(log), 1)
|
||||
|
||||
rule.invalidate_recordset()
|
||||
self.assertEqual(rule.log_count, 1)
|
||||
|
||||
self.assertEqual(log.status, 'success')
|
||||
self.assertEqual(log.post_id.id, post.id)
|
||||
self.assertEqual(log.trigger_keyword, 'fiyat')
|
||||
|
||||
def test_03_no_auto_reply_if_no_keyword(self):
|
||||
"""Kural kelimesi eşleşmediğinde log oluşmadığını test et."""
|
||||
rule = self.env['mymach.social.auto.reply'].create({
|
||||
'name': 'Şikayet Kuralı',
|
||||
'keyword': 'şikayet',
|
||||
'reply_message': 'Lütfen destek hattını arayın.',
|
||||
'active': True
|
||||
})
|
||||
|
||||
# 'fiyat' geçen ama 'şikayet' geçmeyen mesaj
|
||||
self.env['mymach.social.stream.post'].create({
|
||||
'stream_id': self.stream.id,
|
||||
'author_name': 'Müşteri 2',
|
||||
'message': 'Sadece fiyat sormuştum.',
|
||||
'message_type': 'comment'
|
||||
})
|
||||
|
||||
self.assertEqual(rule.log_count, 0)
|
||||
43
extra-addons/mymach_social_nextgen/tests/test_social_core.py
Normal file
43
extra-addons/mymach_social_nextgen/tests/test_social_core.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
class TestSocialCore(TransactionCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSocialCore, cls).setUpClass()
|
||||
# Test hesabı oluştur
|
||||
cls.social_account = cls.env['mymach.social.account'].create({
|
||||
'name': 'Test Account',
|
||||
'platform': 'facebook',
|
||||
'active': True,
|
||||
})
|
||||
|
||||
def test_01_social_account_creation(self):
|
||||
"""Sosyal hesabın doğru ilişkilerle oluşturulduğunu test et."""
|
||||
self.assertEqual(self.social_account.platform, 'facebook')
|
||||
self.assertTrue(self.social_account.active)
|
||||
|
||||
def test_02_social_post_creation(self):
|
||||
"""Sosyal gönderi oluşturma ve durum (state) doğrulama."""
|
||||
post = self.env['mymach.social.post'].create({
|
||||
'name': 'Test Gönderisi',
|
||||
'content': 'Bu bir test gönderisidir.',
|
||||
'account_ids': [(4, self.social_account.id)]
|
||||
})
|
||||
self.assertEqual(post.state, 'draft')
|
||||
self.assertIn(self.social_account, post.account_ids)
|
||||
|
||||
# Post the message
|
||||
post.action_publish()
|
||||
self.assertEqual(post.state, 'published')
|
||||
|
||||
def test_03_social_stream_creation(self):
|
||||
"""Sosyal akış (stream) oluşturmayı test et."""
|
||||
stream = self.env['mymach.social.stream'].create({
|
||||
'name': 'Test Akışı',
|
||||
'account_id': self.social_account.id,
|
||||
'stream_type': 'feed'
|
||||
})
|
||||
self.assertEqual(stream.name, 'Test Akışı')
|
||||
self.assertEqual(stream.account_id.name, 'Test Account')
|
||||
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.social</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="priority" eval="90"/>
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form" position="inside">
|
||||
<app data-string="MyMach Social" string="MyMach Social" name="mymach_social_nextgen" groups="base.group_system">
|
||||
<block title="Sosyal Medya API Kimlik Bilgileri" name="social_api_credentials_setting_container">
|
||||
<setting string="Meta (Facebook/Instagram)" help="Meta Geliştirici Uygulaması için Client ID ve Secret">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="social_meta_client_id" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_meta_client_id" class="oe_inline"/>
|
||||
</div>
|
||||
<div class="row mt8">
|
||||
<label for="social_meta_client_secret" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_meta_client_secret" class="oe_inline" password="True"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
<setting string="LinkedIn" help="LinkedIn Geliştirici Uygulaması için Client ID ve Secret">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="social_linkedin_client_id" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_linkedin_client_id" class="oe_inline"/>
|
||||
</div>
|
||||
<div class="row mt8">
|
||||
<label for="social_linkedin_client_secret" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_linkedin_client_secret" class="oe_inline" password="True"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
<setting string="X (Twitter)" help="X Geliştirici Portalı için Client ID ve Secret">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="social_x_client_id" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_x_client_id" class="oe_inline"/>
|
||||
</div>
|
||||
<div class="row mt8">
|
||||
<label for="social_x_client_secret" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_x_client_secret" class="oe_inline" password="True"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
<setting string="YouTube" help="Google Cloud Console için Client ID ve Secret">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="social_youtube_client_id" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_youtube_client_id" class="oe_inline"/>
|
||||
</div>
|
||||
<div class="row mt8">
|
||||
<label for="social_youtube_client_secret" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_youtube_client_secret" class="oe_inline" password="True"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
</block>
|
||||
<block title="Yapay Zeka Asistan Ayarları" name="social_ai_settings_container">
|
||||
<setting string="Google Gemini" help="Gemini Pro Entegrasyonu için API Anahtarı">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="social_gemini_api_key" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_gemini_api_key" class="oe_inline" password="True"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
<setting string="OpenAI (ChatGPT)" help="OpenAI modelleri için API Anahtarı">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="social_openai_api_key" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_openai_api_key" class="oe_inline" password="True"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
<setting string="Anthropic (Claude)" help="Claude AI modelleri için API Anahtarı">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="social_claude_api_key" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_claude_api_key" class="oe_inline" password="True"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
<setting string="xAI (Grok)" help="Grok modelleri için API Anahtarı">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="social_grok_api_key" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_grok_api_key" class="oe_inline" password="True"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
</block>
|
||||
<block title="Medya Kütüphanesi & Filigran (Watermark) Ayarları" name="social_watermark_settings_container">
|
||||
<setting string="Şirket Logosu (Filigran)" help="Üretilen veya yüklenen medyaların üzerine otomatik basılacak PNG formatında şeffaf logo.">
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="social_watermark_image" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_watermark_image" widget="image" class="oe_avatar"/>
|
||||
</div>
|
||||
<div class="row mt8">
|
||||
<label for="social_watermark_position" class="col-lg-3 o_light_label"/>
|
||||
<field name="social_watermark_position" class="oe_inline"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
</block>
|
||||
</app>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action for Settings -->
|
||||
<record id="action_social_config_settings" model="ir.actions.act_window">
|
||||
<field name="name">Ayarlar</field>
|
||||
<field name="res_model">res.config.settings</field>
|
||||
<field name="view_id" ref="res_config_settings_view_form"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="context">{'module' : 'mymach_social_nextgen', 'bin_size': False}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_social_settings"
|
||||
name="Ayarlar"
|
||||
parent="menu_social_configuration"
|
||||
action="action_social_config_settings"
|
||||
sequence="100"/>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Tree View -->
|
||||
<record id="view_social_account_tree" model="ir.ui.view">
|
||||
<field name="name">mymach.social.account.list</field>
|
||||
<field name="model">mymach.social.account</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Social Accounts">
|
||||
<field name="name"/>
|
||||
<field name="platform"/>
|
||||
<field name="active"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Form View -->
|
||||
<record id="view_social_account_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.account.form</field>
|
||||
<field name="model">mymach.social.account</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Sosyal Medya Hesabı">
|
||||
<header>
|
||||
<button name="action_authenticate" string="Platforma Bağlan" type="object" class="oe_highlight" invisible="access_token != False"/>
|
||||
<button name="action_refresh_token" string="Token Yenile" type="object" invisible="access_token == False"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_authenticate" type="object" class="oe_stat_button" icon="fa-users">
|
||||
<field name="audience_count" widget="statinfo" string="Takipçiler"/>
|
||||
</button>
|
||||
<button name="action_authenticate" type="object" class="oe_stat_button" icon="fa-line-chart">
|
||||
<field name="engagement_rate" widget="statinfo" string="Etkileşim %"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<h1><field name="name" placeholder="Hesap Adı..."/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="platform"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="platform_account_id" invisible="platform_account_id == False"/>
|
||||
<field name="token_expiration" invisible="access_token == False"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_social_account" model="ir.actions.act_window">
|
||||
<field name="name">Sosyal Hesaplar</field>
|
||||
<field name="res_model">mymach.social.account</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create your first Social Media Account connection!
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Graph View -->
|
||||
<record id="view_social_post_graph" model="ir.ui.view">
|
||||
<field name="name">mymach.social.post.graph</field>
|
||||
<field name="model">mymach.social.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Sosyal Medya Analizleri" type="bar" sample="1">
|
||||
<field name="scheduled_date" interval="month"/>
|
||||
<field name="state"/>
|
||||
<field name="likes_count" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Pivot View for Analytics -->
|
||||
<record id="view_social_post_pivot" model="ir.ui.view">
|
||||
<field name="name">mymach.social.post.pivot</field>
|
||||
<field name="model">mymach.social.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Analiz Tablosu" sample="1">
|
||||
<field name="scheduled_date" type="row" interval="month"/>
|
||||
<field name="state" type="col"/>
|
||||
<field name="likes_count" type="measure"/>
|
||||
<field name="comments_count" type="measure"/>
|
||||
<field name="shares_count" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Sentiment Analysis Pie Chart -->
|
||||
<record id="view_social_sentiment_graph" model="ir.ui.view">
|
||||
<field name="name">mymach.social.sentiment.graph</field>
|
||||
<field name="model">mymach.social.stream.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Duygu Analizi" type="pie" sample="1">
|
||||
<field name="sentiment"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_social_sentiment_dashboard" model="ir.actions.act_window">
|
||||
<field name="name">Duygu Analizi Panosu</field>
|
||||
<field name="res_model">mymach.social.stream.post</field>
|
||||
<field name="view_mode">graph,list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Müşteri Geri Bildirimleri Analizi
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Dashboard Action -->
|
||||
<record id="action_social_dashboard" model="ir.actions.act_window">
|
||||
<field name="name">Analiz Dashboard</field>
|
||||
<field name="res_model">mymach.social.post</field>
|
||||
<field name="view_mode">graph,pivot,kanban,list</field>
|
||||
<field name="context">{'search_default_state': 'published'}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Sosyal Medya Performansınızı Takip Edin!
|
||||
</p>
|
||||
<p>
|
||||
Gönderilerinizi yayınladıkça etkileşim grafikleriniz burada belirecektir.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Log List (Tree) View -->
|
||||
<record id="view_social_auto_reply_log_tree" model="ir.ui.view">
|
||||
<field name="name">mymach.social.auto.reply.log.tree</field>
|
||||
<field name="model">mymach.social.auto.reply.log</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Otomatik Yanıt Geçmişi" decoration-success="status == 'success'" decoration-danger="status == 'failed'">
|
||||
<field name="date" widget="datetime"/>
|
||||
<field name="rule_id"/>
|
||||
<field name="author_name"/>
|
||||
<field name="account_id"/>
|
||||
<field name="trigger_keyword"/>
|
||||
<field name="status" widget="badge" decoration-success="status == 'success'" decoration-danger="status == 'failed'"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Log Form View -->
|
||||
<record id="view_social_auto_reply_log_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.auto.reply.log.form</field>
|
||||
<field name="model">mymach.social.auto.reply.log</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Otomatik Yanıt İşlemi">
|
||||
<header>
|
||||
<field name="status" widget="statusbar" statusbar_visible="success,failed"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="rule_id" readonly="1"/>
|
||||
<field name="date" readonly="1"/>
|
||||
<field name="account_id" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="author_name" readonly="1"/>
|
||||
<field name="trigger_keyword" readonly="1"/>
|
||||
<field name="post_id" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Gönderilen Yanıt">
|
||||
<field name="sent_message" readonly="1"/>
|
||||
</page>
|
||||
<page string="Hata Mesajı" invisible="status == 'success'">
|
||||
<field name="error_message" readonly="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Search View -->
|
||||
<record id="view_social_auto_reply_log_search" model="ir.ui.view">
|
||||
<field name="name">mymach.social.auto.reply.log.search</field>
|
||||
<field name="model">mymach.social.auto.reply.log</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="rule_id"/>
|
||||
<field name="author_name"/>
|
||||
<field name="account_id"/>
|
||||
|
||||
<separator/>
|
||||
<filter string="Başarılı" name="success" domain="[('status', '=', 'success')]"/>
|
||||
<filter string="Başarısız" name="failed" domain="[('status', '=', 'failed')]"/>
|
||||
|
||||
<separator/>
|
||||
<filter string="Kurala Göre Grupla" name="group_rule" context="{'group_by':'rule_id'}"/>
|
||||
<filter string="Tarihe Göre Grupla" name="group_date" context="{'group_by':'date:day'}"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_social_auto_reply_log" model="ir.actions.act_window">
|
||||
<field name="name">İşlem Geçmişi</field>
|
||||
<field name="res_model">mymach.social.auto.reply.log</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_social_auto_reply_log_search"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Form View -->
|
||||
<record id="view_social_auto_reply_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.auto.reply.form</field>
|
||||
<field name="model">mymach.social.auto.reply</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Otomatik Yanıt Kuralı">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_view_logs" type="object" class="oe_stat_button" icon="fa-history" invisible="log_count == 0">
|
||||
<field name="log_count" widget="statinfo" string="İşlemler"/>
|
||||
</button>
|
||||
</div>
|
||||
<widget name="web_ribbon" title="Pasif" bg_color="bg-danger" invisible="active == True"/>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Kural Adı (Örn: Bilgi Talebi)"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Kural Ayarları">
|
||||
<field name="keyword" placeholder="bilgi"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
<field name="sequence"/>
|
||||
</group>
|
||||
<group string="Filtreler">
|
||||
<field name="apply_to_message_type" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="account_ids" widget="many2many_tags" placeholder="Tüm hesaplar"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Yanıt İçeriği">
|
||||
<field name="reply_message" nolabel="1" widget="text" placeholder="Gelecek olan mesajda bu kelime geçiyorsa otomatik olarak şu metni gönder..."/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- List View -->
|
||||
<record id="view_social_auto_reply_list" model="ir.ui.view">
|
||||
<field name="name">mymach.social.auto.reply.list</field>
|
||||
<field name="model">mymach.social.auto.reply</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Otomatik Yanıt Kuralları">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="keyword"/>
|
||||
<field name="active" widget="boolean_toggle"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_social_auto_reply" model="ir.actions.act_window">
|
||||
<field name="name">Otomatik Yanıt Kuralları</field>
|
||||
<field name="res_model">mymach.social.auto.reply</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
İlk Otomatik Yanıt Kuralınızı Oluşturun!
|
||||
</p>
|
||||
<p>
|
||||
Gelen yorum veya mesajlarda spesifik anahtar kelimeler ("bilgi", "fiyat" vb.) geçtiğinde sistemin otomatik yanıt vermesini sağlayabilirsiniz.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- List View -->
|
||||
<record id="view_social_competitor_list" model="ir.ui.view">
|
||||
<field name="name">social.competitor.list</field>
|
||||
<field name="model">social.competitor</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Rakipler">
|
||||
<field name="name"/>
|
||||
<field name="platform"/>
|
||||
<field name="followers_count"/>
|
||||
<field name="engagement_rate"/>
|
||||
<field name="last_scraped"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Form View -->
|
||||
<record id="view_social_competitor_form" model="ir.ui.view">
|
||||
<field name="name">social.competitor.form</field>
|
||||
<field name="model">social.competitor</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Rakip Bilgisi">
|
||||
<header>
|
||||
<button name="action_scrape_data" string="Verileri Kazı (Güncelle)" type="object" class="oe_highlight"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Rakip Firma Adı"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="platform"/>
|
||||
<field name="profile_url" widget="url"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="followers_count"/>
|
||||
<field name="engagement_rate"/>
|
||||
<field name="last_scraped"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Analiz Notları">
|
||||
<field name="notes" placeholder="Stratejik notlar..."/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Kanban View -->
|
||||
<record id="view_social_competitor_kanban" model="ir.ui.view">
|
||||
<field name="name">social.competitor.kanban</field>
|
||||
<field name="model">social.competitor</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_dashboard">
|
||||
<field name="name"/>
|
||||
<field name="platform"/>
|
||||
<field name="followers_count"/>
|
||||
<field name="engagement_rate"/>
|
||||
<template>
|
||||
<t t-name="card">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="o_kanban_record_title">
|
||||
<strong><field name="name"/></strong>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<span class="badge rounded-pill text-bg-primary"><field name="platform"/></span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<i class="fa fa-users" title="Takipçiler"/> <field name="followers_count"/>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<i class="fa fa-line-chart" title="Etkileşim Oranı"/> <field name="engagement_rate"/> %
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Graph View -->
|
||||
<record id="view_social_competitor_graph" model="ir.ui.view">
|
||||
<field name="name">social.competitor.graph</field>
|
||||
<field name="model">social.competitor</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Rakip Takipçi Karşılaştırması" type="bar">
|
||||
<field name="name"/>
|
||||
<field name="followers_count" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_social_competitor" model="ir.actions.act_window">
|
||||
<field name="name">Rakipler</field>
|
||||
<field name="res_model">social.competitor</field>
|
||||
<field name="view_mode">kanban,list,graph,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
İlk Rakibinizi Ekleyin!
|
||||
</p>
|
||||
<p>
|
||||
Rakiplerinizin sosyal medya hesaplarını ekleyerek durumlarını izleyin.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu Item -->
|
||||
<menuitem id="menu_social_competitors"
|
||||
name="Rakipler"
|
||||
parent="mymach_social_nextgen.menu_social_root"
|
||||
action="action_social_competitor"
|
||||
sequence="35"/>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_social_live_post_list" model="ir.ui.view">
|
||||
<field name="name">mymach.social.live.post.list</field>
|
||||
<field name="model">mymach.social.live.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Canlı Gönderiler" decoration-info="state == 'ready'" decoration-success="state == 'posted'" decoration-danger="state == 'failed'">
|
||||
<field name="account_id"/>
|
||||
<field name="post_id"/>
|
||||
<field name="state" widget="badge"/>
|
||||
<field name="likes_count"/>
|
||||
<field name="comments_count"/>
|
||||
<field name="shares_count"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_social_live_post" model="ir.actions.act_window">
|
||||
<field name="name">Canlı Gönderiler</field>
|
||||
<field name="res_model">mymach.social.live.post</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="domain">[('post_id', '=', active_id)]</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Media Form View -->
|
||||
<record id="view_social_media_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.media.form</field>
|
||||
<field name="model">mymach.social.media</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Medya Kütüphanesi">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Medya Adı..."/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="media_type"/>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="attachment_id"/>
|
||||
<field name="image_preview" widget="image" class="oe_avatar" invisible="media_type != 'image'"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Media List View -->
|
||||
<record id="view_social_media_list" model="ir.ui.view">
|
||||
<field name="name">mymach.social.media.list</field>
|
||||
<field name="model">mymach.social.media</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Medya Kütüphanesi">
|
||||
<field name="name"/>
|
||||
<field name="media_type"/>
|
||||
<field name="tag_ids" widget="many2many_tags"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Media Kanban View -->
|
||||
<record id="view_social_media_kanban" model="ir.ui.view">
|
||||
<field name="name">mymach.social.media.kanban</field>
|
||||
<field name="model">mymach.social.media</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="name"/>
|
||||
<field name="media_type"/>
|
||||
<field name="image_preview"/>
|
||||
<field name="tag_ids"/>
|
||||
<template>
|
||||
<t t-name="card">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="o_kanban_image">
|
||||
<field name="image_preview" widget="image" options="{'size': [90, 90]}"/>
|
||||
</div>
|
||||
<div class="oe_kanban_details">
|
||||
<strong><field name="name"/></strong>
|
||||
<div><field name="media_type"/></div>
|
||||
<div><field name="tag_ids" widget="many2many_tags"/></div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Media Action -->
|
||||
<record id="action_social_media" model="ir.actions.act_window">
|
||||
<field name="name">Medya Kütüphanesi</field>
|
||||
<field name="res_model">mymach.social.media</field>
|
||||
<field name="view_mode">kanban,list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Medya Kütüphanenize ilk dosyanızı ekleyin!
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
139
extra-addons/mymach_social_nextgen/views/social_menu.xml
Normal file
139
extra-addons/mymach_social_nextgen/views/social_menu.xml
Normal file
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Top Level Menu -->
|
||||
<menuitem id="menu_social_root"
|
||||
name="MyMach Social"
|
||||
web_icon="mymach_social_nextgen,static/description/icon.png"
|
||||
sequence="50"/>
|
||||
|
||||
<!-- Dashboard (Default Home) -->
|
||||
<menuitem id="menu_social_dashboard"
|
||||
name="Kontrol Paneli"
|
||||
parent="menu_social_root"
|
||||
action="action_social_dashboard_kanban"
|
||||
sequence="10"/>
|
||||
|
||||
<!-- Calendar Action -->
|
||||
<record id="action_social_calendar" model="ir.actions.act_window">
|
||||
<field name="name">Takvim</field>
|
||||
<field name="res_model">mymach.social.post</field>
|
||||
<field name="view_mode">calendar,kanban,list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Aylık Gönderi Planınız Burada Görünecek!
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Sentiment Dashboard -->
|
||||
<menuitem id="menu_social_sentiment"
|
||||
name="Duygu Analizi"
|
||||
parent="menu_social_root"
|
||||
action="action_social_sentiment_dashboard"
|
||||
sequence="12"/>
|
||||
|
||||
<!-- Inbox / Live Chat -->
|
||||
<menuitem id="menu_social_inbox"
|
||||
name="Gelen Kutusu (Sohbet)"
|
||||
parent="menu_social_root"
|
||||
action="action_social_inbox"
|
||||
sequence="15"/>
|
||||
|
||||
<!-- Calendar Menu -->
|
||||
<menuitem id="menu_social_calendar"
|
||||
name="Takvim"
|
||||
parent="menu_social_root"
|
||||
action="action_social_calendar"
|
||||
sequence="18"/>
|
||||
|
||||
<!-- Posts Menu -->
|
||||
<menuitem id="menu_social_posts"
|
||||
name="Gönderiler"
|
||||
parent="menu_social_root"
|
||||
action="action_social_post"
|
||||
sequence="20"/>
|
||||
|
||||
<!-- Streams Menu -->
|
||||
<menuitem id="menu_social_streams"
|
||||
name="Akışlar (Streams)"
|
||||
parent="menu_social_root"
|
||||
action="action_social_stream"
|
||||
sequence="25"/>
|
||||
|
||||
<!-- Campaigns Action -->
|
||||
<record id="action_social_campaign" model="ir.actions.act_window">
|
||||
<field name="name">Kampanyalar</field>
|
||||
<field name="res_model">utm.campaign</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Pazarlama Kampanyalarınızı Buradan Yönetin
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Campaigns Menu -->
|
||||
<menuitem id="menu_social_campaigns"
|
||||
name="Kampanyalar"
|
||||
parent="menu_social_root"
|
||||
action="action_social_campaign"
|
||||
sequence="30"/>
|
||||
|
||||
<!-- Media Library Menu -->
|
||||
<menuitem id="menu_social_media_library"
|
||||
name="Medya Kütüphanesi"
|
||||
parent="menu_social_root"
|
||||
action="action_social_media"
|
||||
sequence="35"/>
|
||||
|
||||
<!-- Visitors Action -->
|
||||
<record id="action_social_visitor" model="ir.actions.act_window">
|
||||
<field name="name">Ziyaretçiler (Tıklamalar)</field>
|
||||
<field name="res_model">link.tracker.click</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{'create': False, 'edit': False}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Sosyal Medya Ziyaretçilerinizi (Tıklamaları) Buradan Takip Edin
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Visitors Menu -->
|
||||
<menuitem id="menu_social_visitors"
|
||||
name="Ziyaretçiler"
|
||||
parent="menu_social_root"
|
||||
action="action_social_visitor"
|
||||
sequence="40"/>
|
||||
|
||||
<!-- Configuration Menu -->
|
||||
<menuitem id="menu_social_configuration"
|
||||
name="Yapılandırma"
|
||||
parent="menu_social_root"
|
||||
sequence="100"/>
|
||||
|
||||
<!-- Accounts Menu (Under Configuration) -->
|
||||
<menuitem id="menu_social_accounts_config"
|
||||
name="Sosyal Hesaplar"
|
||||
parent="menu_social_configuration"
|
||||
action="action_social_account"
|
||||
sequence="10"/>
|
||||
|
||||
<!-- Auto-Reply Rules Menu (Under Configuration) -->
|
||||
<menuitem id="menu_social_auto_reply_config"
|
||||
name="Otomatik Yanıt Kuralları"
|
||||
parent="menu_social_configuration"
|
||||
action="action_social_auto_reply"
|
||||
sequence="20"/>
|
||||
|
||||
<!-- Auto-Reply Logs Menu (Under Configuration) -->
|
||||
<menuitem id="menu_social_auto_reply_logs_config"
|
||||
name="Otomatik Yanıt Geçmişi"
|
||||
parent="menu_social_configuration"
|
||||
action="action_social_auto_reply_log"
|
||||
sequence="30"/>
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
144
extra-addons/mymach_social_nextgen/views/social_post_views.xml
Normal file
144
extra-addons/mymach_social_nextgen/views/social_post_views.xml
Normal file
@@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Form View -->
|
||||
<record id="view_social_post_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.post.form</field>
|
||||
<field name="model">mymach.social.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Sosyal Medya Gönderisi">
|
||||
<header>
|
||||
<button name="action_open_ai_wizard" string="✨ AI ile İçerik Üret" type="object" class="btn-info" invisible="state != 'draft'"/>
|
||||
<button name="action_open_ai_image_wizard" string="🎨 AI Görsel Üret" type="object" class="btn-primary" invisible="state != 'draft'"/>
|
||||
<button name="action_request_approval" string="Onaya Gönder" type="object" invisible="state != 'draft'"/>
|
||||
<button name="action_schedule" string="Planla" type="object" class="oe_highlight" invisible="state not in ('draft', 'waiting')" groups="mymach_social_nextgen.group_social_manager"/>
|
||||
<button name="action_publish_now" string="Onayla ve Yayınla" type="object" class="btn-warning" invisible="state not in ('draft', 'waiting')" groups="mymach_social_nextgen.group_social_manager"/>
|
||||
<button name="action_reset_to_draft" string="Taslağa Çevir" type="object" invisible="state == 'draft'" groups="mymach_social_nextgen.group_social_manager"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,waiting,scheduled,published"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="%(action_social_live_post)d" type="action" class="oe_stat_button" icon="fa-share-square-o" invisible="state not in ('published', 'failed')">
|
||||
<field name="live_post_ids" widget="statinfo" string="Canlı Gönderiler"/>
|
||||
</button>
|
||||
<button name="action_view_visitors" type="object" class="oe_stat_button" icon="fa-mouse-pointer" invisible="state not in ('published', 'failed')">
|
||||
<field name="click_count" widget="statinfo" string="Ziyaretçiler"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Gönderi Başlığı..."/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="account_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
<field name="scheduled_date" invisible="state == 'published'"/>
|
||||
<field name="published_date" invisible="state != 'published'"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="media_ids" widget="many2many_binary" string="Görseller (Ekler)"/>
|
||||
<field name="post_type" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="utm_campaign_id"/>
|
||||
<field name="product_id" string="E-Ticaret Ürünü" help="Seçilen ürünün satın alma linki ve fiyatı paylaşıma otomatik eklenecek."/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="İçerik">
|
||||
<group>
|
||||
<group>
|
||||
<field name="content" widget="text" placeholder="Gönderinizi buraya yazın..." nolabel="1" colspan="2"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="preview_html" widget="html" nolabel="1" colspan="2" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Hata Mesajı" invisible="state != 'failed'">
|
||||
<field name="error_message"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- List View -->
|
||||
<record id="view_social_post_list" model="ir.ui.view">
|
||||
<field name="name">mymach.social.post.list</field>
|
||||
<field name="model">mymach.social.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Sosyal Medya Gönderileri" decoration-info="state == 'scheduled'" decoration-success="state == 'published'" decoration-danger="state == 'failed'" decoration-warning="state == 'waiting'">
|
||||
<field name="name"/>
|
||||
<field name="account_ids" widget="many2many_tags"/>
|
||||
<field name="scheduled_date"/>
|
||||
<field name="published_date"/>
|
||||
<field name="state" widget="badge"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Kanban View -->
|
||||
<record id="view_social_post_kanban" model="ir.ui.view">
|
||||
<field name="name">mymach.social.post.kanban</field>
|
||||
<field name="model">mymach.social.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="state" class="o_kanban_small_column" quick_create="false">
|
||||
<field name="name"/>
|
||||
<field name="state"/>
|
||||
<field name="color"/>
|
||||
<field name="scheduled_date"/>
|
||||
<field name="account_ids"/>
|
||||
<template>
|
||||
<t t-name="card">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="oe_kanban_content">
|
||||
<div class="o_kanban_record_title">
|
||||
<strong><field name="name"/></strong>
|
||||
</div>
|
||||
<div class="o_kanban_record_bottom mt-2">
|
||||
<div class="oe_kanban_bottom_left">
|
||||
<field name="account_ids" widget="many2many_tags"/>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="scheduled_date"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Calendar View -->
|
||||
<record id="view_social_post_calendar" model="ir.ui.view">
|
||||
<field name="name">mymach.social.post.calendar</field>
|
||||
<field name="model">mymach.social.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<calendar string="Sosyal Medya Gönderileri" date_start="scheduled_date" color="state" mode="month" quick_create="0">
|
||||
<field name="name"/>
|
||||
<field name="account_ids"/>
|
||||
<field name="state" filters="1"/>
|
||||
</calendar>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_social_post" model="ir.actions.act_window">
|
||||
<field name="name">Gönderiler</field>
|
||||
<field name="res_model">mymach.social.post</field>
|
||||
<field name="view_mode">kanban,list,calendar,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
İlk Sosyal Medya Gönderinizi Oluşturun!
|
||||
</p>
|
||||
<p>
|
||||
İçeriklerinizi hazırlayın, medyalarınızı ekleyin ve planlayın.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Social Dashboard Kanban View -->
|
||||
<record id="view_social_stream_post_kanban_dashboard" model="ir.ui.view">
|
||||
<field name="name">mymach.social.stream.post.kanban.dashboard</field>
|
||||
<field name="model">mymach.social.stream.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_social_dashboard_kanban" default_group_by="stream_id" create="0">
|
||||
<field name="author_name"/>
|
||||
<field name="published_date"/>
|
||||
<field name="message"/>
|
||||
<field name="likes_count"/>
|
||||
<field name="comments_count"/>
|
||||
<field name="shares_count"/>
|
||||
<field name="stream_id"/>
|
||||
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<div t-attf-class="oe_kanban_global_click o_kanban_record">
|
||||
<!-- Author & Time Info -->
|
||||
<div class="o_social_author_section">
|
||||
<div class="o_social_author_avatar d-flex align-items-center justify-content-center text-white">
|
||||
<i class="fa fa-user"/>
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<span class="o_social_author_name"><field name="author_name"/></span>
|
||||
<span class="o_social_time"><field name="published_date"/></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Post Content -->
|
||||
<div class="o_social_message_text">
|
||||
<field name="message"/>
|
||||
</div>
|
||||
|
||||
<!-- Actions / Metrics -->
|
||||
<div class="o_social_actions">
|
||||
<div title="Beğeniler">
|
||||
<i class="fa fa-heart-o me-1"/> <field name="likes_count"/>
|
||||
</div>
|
||||
<div title="Yorumlar">
|
||||
<i class="fa fa-comment-o me-1"/> <field name="comments_count"/>
|
||||
</div>
|
||||
<div title="Paylaşımlar">
|
||||
<i class="fa fa-share-square-o me-1"/> <field name="shares_count"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Dashboard Action -->
|
||||
<record id="action_social_dashboard_kanban" model="ir.actions.act_window">
|
||||
<field name="name">Kontrol Paneli</field>
|
||||
<field name="res_model">mymach.social.stream.post</field>
|
||||
<field name="view_mode">kanban,form</field>
|
||||
<field name="view_id" ref="view_social_stream_post_kanban_dashboard"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Harika Sosyal Medya Kontrol Panelinize Hoşgeldiniz!
|
||||
</p>
|
||||
<p>
|
||||
Burada yapılandırdığınız akışlar, ekranınızda sütunlar halinde listelenecektir.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
148
extra-addons/mymach_social_nextgen/views/social_stream_views.xml
Normal file
148
extra-addons/mymach_social_nextgen/views/social_stream_views.xml
Normal file
@@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Stream Kanban View -->
|
||||
<record id="view_social_stream_kanban" model="ir.ui.view">
|
||||
<field name="name">mymach.social.stream.kanban</field>
|
||||
<field name="model">mymach.social.stream</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_dashboard" create="1">
|
||||
<field name="name"/>
|
||||
<field name="account_id"/>
|
||||
<field name="stream_type"/>
|
||||
<field name="search_keyword"/>
|
||||
<template>
|
||||
<t t-name="card">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="o_kanban_record_title">
|
||||
<strong><field name="name"/></strong>
|
||||
</div>
|
||||
<div><field name="account_id"/></div>
|
||||
<div><field name="stream_type"/></div>
|
||||
<div t-if="record.search_keyword.raw_value" class="text-info">
|
||||
<i class="fa fa-search" title="Aranan Kelime"/> <field name="search_keyword"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Stream Form View -->
|
||||
<record id="view_social_stream_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.stream.form</field>
|
||||
<field name="model">mymach.social.stream</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="account_id"/>
|
||||
<field name="stream_type"/>
|
||||
<field name="search_keyword" invisible="stream_type != 'keyword'" required="stream_type == 'keyword'"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Gönderiler ve Yorumlar">
|
||||
<field name="stream_post_ids">
|
||||
<list string="Akış Gönderileri">
|
||||
<field name="author_name"/>
|
||||
<field name="message"/>
|
||||
<field name="likes_count"/>
|
||||
<field name="comments_count"/>
|
||||
<button name="action_create_lead" type="object" string="CRM Fırsatı Yarat" icon="fa-star" class="btn-success"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_social_stream" model="ir.actions.act_window">
|
||||
<field name="name">Akışlar (Streams)</field>
|
||||
<field name="res_model">mymach.social.stream</field>
|
||||
<field name="view_mode">kanban,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
İlk Sosyal Medya Akışınızı Ekleyin!
|
||||
</p>
|
||||
<p>
|
||||
Sosyal medya hesaplarınıza gelen yorum ve mesajları buradan canlı takip edebilirsiniz.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Live Chat (Inbox) Action -->
|
||||
<record id="action_social_inbox" model="ir.actions.act_window">
|
||||
<field name="name">Canlı Sohbet (Gelen Kutusu)</field>
|
||||
<field name="res_model">mymach.social.stream.post</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Sosyal Medya Mesajlarınız Burada Toplanacak!
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Live Chat List View -->
|
||||
<record id="view_social_stream_post_list" model="ir.ui.view">
|
||||
<field name="name">mymach.social.stream.post.list</field>
|
||||
<field name="model">mymach.social.stream.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Gelen Mesajlar" decoration-info="True" create="0">
|
||||
<field name="published_date"/>
|
||||
<field name="author_name" string="Gönderen"/>
|
||||
<field name="account_id" string="Hesap"/>
|
||||
<field name="message_type" widget="badge" decoration-info="message_type == 'direct_message'" decoration-muted="message_type == 'comment'"/>
|
||||
<field name="message" string="Mesaj İçeriği"/>
|
||||
<field name="sentiment" widget="badge" decoration-success="sentiment == 'positive'" decoration-danger="sentiment == 'negative'" decoration-warning="sentiment == 'neutral'"/>
|
||||
<button name="action_analyze_sentiment" type="object" string="Duyguyu Analiz Et" icon="fa-smile-o"/>
|
||||
<button name="action_create_lead" type="object" string="Fırsat Yarat" icon="fa-star" class="btn-success"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Live Chat Form View -->
|
||||
<record id="view_social_stream_post_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.stream.post.form</field>
|
||||
<field name="model">mymach.social.stream.post</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mesaj Detayı" create="0">
|
||||
<header>
|
||||
<button name="action_suggest_reply" type="object" string="✨ AI ile Yanıt Öner" class="btn-info"/>
|
||||
<button name="action_analyze_sentiment" type="object" string="Duyguyu Analiz Et"/>
|
||||
<button name="action_create_lead" type="object" string="CRM Fırsatı Yarat" class="oe_highlight"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="author_name" readonly="1"/>
|
||||
<field name="account_id" readonly="1"/>
|
||||
<field name="published_date" readonly="1"/>
|
||||
<field name="message_type" widget="badge" decoration-info="message_type == 'direct_message'" decoration-muted="message_type == 'comment'"/>
|
||||
<field name="sentiment" widget="badge" decoration-success="sentiment == 'positive'" decoration-danger="sentiment == 'negative'" decoration-warning="sentiment == 'neutral'"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="likes_count" readonly="1"/>
|
||||
<field name="comments_count" readonly="1"/>
|
||||
<field name="shares_count" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Mesaj İçeriği">
|
||||
<field name="message" widget="text" readonly="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
3
extra-addons/mymach_social_nextgen/wizard/__init__.py
Normal file
3
extra-addons/mymach_social_nextgen/wizard/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import social_ai_wizard
|
||||
from . import social_ai_image_wizard
|
||||
from . import social_ai_reply_wizard
|
||||
@@ -0,0 +1,43 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class SocialAIImageWizard(models.TransientModel):
|
||||
_name = 'mymach.social.ai.image.wizard'
|
||||
_description = 'AI Image Generator Wizard'
|
||||
|
||||
post_id = fields.Many2one('mymach.social.post', string='Post', required=True)
|
||||
image_prompt = fields.Text(string='Görsel Tarifi', required=True,
|
||||
default='Örn: Masada duran şık bir kahve fincanı, arka planda gün batımı.')
|
||||
|
||||
def action_generate_image(self):
|
||||
self.ensure_one()
|
||||
|
||||
try:
|
||||
import requests
|
||||
import urllib.parse
|
||||
import base64
|
||||
|
||||
encoded_prompt = urllib.parse.quote(self.image_prompt)
|
||||
|
||||
# Pollinations AI generates images for free without API keys.
|
||||
pollinations_url = f"https://image.pollinations.ai/prompt/{encoded_prompt}?width=1024&height=1024&nologo=true"
|
||||
|
||||
img_data = requests.get(pollinations_url, timeout=30).content
|
||||
encoded_image = base64.b64encode(img_data)
|
||||
|
||||
media_record = self.env['ir.attachment'].create({
|
||||
'name': f"AI_Generated_Image.png",
|
||||
'datas': encoded_image,
|
||||
'res_model': 'mymach.social.post',
|
||||
'res_id': self.post_id.id,
|
||||
'type': 'binary',
|
||||
})
|
||||
|
||||
self.post_id.write({'media_ids': [(4, media_record.id)]})
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(f"Image Generation Error: {str(e)}")
|
||||
raise UserError(f"Görsel üretilirken hata oluştu: {str(e)}")
|
||||
@@ -0,0 +1,141 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class SocialAIReplyWizard(models.TransientModel):
|
||||
_name = 'mymach.social.ai.reply.wizard'
|
||||
_description = 'AI Reply Generator Wizard'
|
||||
|
||||
stream_post_id = fields.Many2one('mymach.social.stream.post', string='Mesaj', required=True)
|
||||
customer_message = fields.Text(related='stream_post_id.message', string="Müşteri Mesajı")
|
||||
suggested_reply = fields.Text(string='Üretilen Yanıt')
|
||||
|
||||
ai_provider = fields.Selection([
|
||||
('gemini', 'Google Gemini'),
|
||||
('openai', 'OpenAI (ChatGPT)'),
|
||||
('claude', 'Anthropic (Claude)'),
|
||||
('grok', 'xAI (Grok)')
|
||||
], string='Yapay Zeka Sağlayıcısı', default='gemini', required=True)
|
||||
|
||||
def action_generate_reply(self):
|
||||
self.ensure_one()
|
||||
|
||||
system_instruction = "Sen profesyonel bir müşteri temsilcisisin. Gelen sosyal medya mesajına uygun, kibar, çözüm odaklı ve kurumsal bir yanıt önerisi oluştur. Kısa ve net ol."
|
||||
user_prompt = f"Müşteri Mesajı: {self.customer_message}\n\nLütfen bu mesaja profesyonel bir yanıt üret."
|
||||
|
||||
try:
|
||||
import requests
|
||||
import json
|
||||
|
||||
generated_text = ""
|
||||
|
||||
if self.ai_provider == 'gemini':
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.gemini_api_key')
|
||||
if not api_key: raise UserError("Google Gemini API Anahtarı eksik!")
|
||||
|
||||
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
|
||||
payload = {
|
||||
"system_instruction": {"parts": [{"text": system_instruction}]},
|
||||
"contents": [{"parts": [{"text": user_prompt}]}],
|
||||
"generationConfig": {"temperature": 0.5, "maxOutputTokens": 200}
|
||||
}
|
||||
response = requests.post(url, headers={'Content-Type': 'application/json'}, json=payload)
|
||||
if response.status_code != 200: raise UserError(f"Gemini API Hatası: {response.text}")
|
||||
generated_text = response.json().get('candidates', [{}])[0].get('content', {}).get('parts', [{}])[0].get('text', '')
|
||||
|
||||
elif self.ai_provider == 'openai':
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.openai_api_key')
|
||||
if not api_key: raise UserError("OpenAI API Anahtarı eksik!")
|
||||
|
||||
url = "https://api.openai.com/v1/chat/completions"
|
||||
headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}'}
|
||||
payload = {
|
||||
"model": "gpt-4o",
|
||||
"messages": [
|
||||
{"role": "system", "content": system_instruction},
|
||||
{"role": "user", "content": user_prompt}
|
||||
],
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 200
|
||||
}
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
if response.status_code != 200: raise UserError(f"OpenAI API Hatası: {response.text}")
|
||||
generated_text = response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
|
||||
|
||||
elif self.ai_provider == 'claude':
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.claude_api_key')
|
||||
if not api_key: raise UserError("Claude API Anahtarı eksik!")
|
||||
|
||||
url = "https://api.anthropic.com/v1/messages"
|
||||
headers = {
|
||||
'x-api-key': api_key,
|
||||
'anthropic-version': '2023-06-01',
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
payload = {
|
||||
"model": "claude-3-5-sonnet-20240620",
|
||||
"system": system_instruction,
|
||||
"messages": [{"role": "user", "content": user_prompt}],
|
||||
"max_tokens": 200,
|
||||
"temperature": 0.5
|
||||
}
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
if response.status_code != 200: raise UserError(f"Claude API Hatası: {response.text}")
|
||||
generated_text = response.json().get('content', [{}])[0].get('text', '')
|
||||
|
||||
elif self.ai_provider == 'grok':
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.grok_api_key')
|
||||
if not api_key: raise UserError("Grok API Anahtarı eksik!")
|
||||
|
||||
url = "https://api.x.ai/v1/chat/completions"
|
||||
headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}'}
|
||||
payload = {
|
||||
"model": "grok-2",
|
||||
"messages": [
|
||||
{"role": "system", "content": system_instruction},
|
||||
{"role": "user", "content": user_prompt}
|
||||
],
|
||||
"temperature": 0.5,
|
||||
"max_tokens": 200
|
||||
}
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
if response.status_code != 200: raise UserError(f"Grok API Hatası: {response.text}")
|
||||
generated_text = response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
|
||||
|
||||
if not generated_text:
|
||||
raise UserError(f"{self.ai_provider} boş bir yanıt döndürdü.")
|
||||
|
||||
self.suggested_reply = generated_text.strip()
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'mymach.social.ai.reply.wizard',
|
||||
'res_id': self.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(f"AI API Error: {str(e)}")
|
||||
raise UserError(f"AI ile iletişim kurulamadı: {str(e)}")
|
||||
|
||||
def action_send_reply(self):
|
||||
self.ensure_one()
|
||||
if not self.suggested_reply:
|
||||
raise UserError("Gönderilecek bir yanıt yok.")
|
||||
|
||||
# In a real app, this would call the Meta Graph API to post the reply to the comment/message.
|
||||
# For our NextGen simulation, we will just log it or simulate it.
|
||||
# In this demo, let's just close the wizard and show a success notification.
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'title': 'Başarılı',
|
||||
'message': 'Mesaj başarıyla müşteriye iletildi!',
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
168
extra-addons/mymach_social_nextgen/wizard/social_ai_wizard.py
Normal file
168
extra-addons/mymach_social_nextgen/wizard/social_ai_wizard.py
Normal file
@@ -0,0 +1,168 @@
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class SocialAIWizard(models.TransientModel):
|
||||
_name = 'mymach.social.ai.wizard'
|
||||
_description = 'AI Content Generator Wizard'
|
||||
|
||||
post_id = fields.Many2one('mymach.social.post', string='Post', required=True)
|
||||
prompt = fields.Text(string='Gönderi Konusu', required=True,
|
||||
default='Örn: Yeni çıkardığımız CNC makinesinin hız ve hassasiyet özelliklerini anlatan bir tanıtım gönderisi.')
|
||||
tone = fields.Selection([
|
||||
('professional', 'Profesyonel'),
|
||||
('casual', 'Günlük ve Samimi'),
|
||||
('humorous', 'Eğlenceli / Esprili'),
|
||||
('formal', 'Resmi'),
|
||||
], string='Ses Tonu', default='professional', required=True)
|
||||
|
||||
include_hashtags = fields.Boolean(string='Trend Hashtag\'ler Eklensin', default=True)
|
||||
|
||||
# Free Image Generation Fields
|
||||
generate_image = fields.Boolean(string='Görsel Üret (Yapay Zeka - Ücretsiz)', default=False)
|
||||
image_prompt = fields.Text(string='Görsel Tarifi', help='Eğer boş bırakılırsa, Gönderi Konusu kullanılacaktır.')
|
||||
|
||||
ai_provider = fields.Selection([
|
||||
('gemini', 'Google Gemini'),
|
||||
('openai', 'OpenAI (ChatGPT)'),
|
||||
('claude', 'Anthropic (Claude)'),
|
||||
('grok', 'xAI (Grok)'),
|
||||
('none', 'Hiçbiri (Sadece Görsel Üret veya Manuel)')
|
||||
], string='Yapay Zeka Sağlayıcısı', default='gemini', required=True)
|
||||
|
||||
def action_generate_content(self):
|
||||
self.ensure_one()
|
||||
|
||||
# --- 1. Handle Text Generation if not 'none' ---
|
||||
generated_text = ""
|
||||
if self.ai_provider != 'none':
|
||||
tone_map = dict(self._fields['tone'].selection).get(self.tone)
|
||||
system_instruction = f"Sen uzman bir sosyal medya yöneticisisin. Verilen konuya göre Türkçe, etkileyici ve ilgi çekici bir sosyal medya gönderisi yaz. Tonlama: {tone_map}."
|
||||
if self.include_hashtags:
|
||||
system_instruction += " Metnin sonuna konuyla ilgili 3-5 adet trend hashtag ekle."
|
||||
|
||||
user_prompt = f"Konu: {self.prompt}\n\nLütfen bu konuya göre sosyal medya içeriğini üret."
|
||||
|
||||
try:
|
||||
import requests
|
||||
import json
|
||||
|
||||
if self.ai_provider == 'gemini':
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.gemini_api_key')
|
||||
if not api_key: raise UserError("Google Gemini API Anahtarı eksik!")
|
||||
|
||||
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
|
||||
payload = {
|
||||
"system_instruction": {"parts": [{"text": system_instruction}]},
|
||||
"contents": [{"parts": [{"text": user_prompt}]}],
|
||||
"generationConfig": {"temperature": 0.7, "maxOutputTokens": 400}
|
||||
}
|
||||
response = requests.post(url, headers={'Content-Type': 'application/json'}, json=payload)
|
||||
if response.status_code != 200: raise UserError(f"Gemini API Hatası: {response.text}")
|
||||
generated_text = response.json().get('candidates', [{}])[0].get('content', {}).get('parts', [{}])[0].get('text', '')
|
||||
|
||||
elif self.ai_provider == 'openai':
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.openai_api_key')
|
||||
if not api_key: raise UserError("OpenAI API Anahtarı eksik!")
|
||||
|
||||
url = "https://api.openai.com/v1/chat/completions"
|
||||
headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}'}
|
||||
payload = {
|
||||
"model": "gpt-4o",
|
||||
"messages": [
|
||||
{"role": "system", "content": system_instruction},
|
||||
{"role": "user", "content": user_prompt}
|
||||
],
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 400
|
||||
}
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
if response.status_code != 200: raise UserError(f"OpenAI API Hatası: {response.text}")
|
||||
generated_text = response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
|
||||
|
||||
elif self.ai_provider == 'claude':
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.claude_api_key')
|
||||
if not api_key: raise UserError("Claude API Anahtarı eksik!")
|
||||
|
||||
url = "https://api.anthropic.com/v1/messages"
|
||||
headers = {
|
||||
'x-api-key': api_key,
|
||||
'anthropic-version': '2023-06-01',
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
payload = {
|
||||
"model": "claude-3-5-sonnet-20240620",
|
||||
"system": system_instruction,
|
||||
"messages": [{"role": "user", "content": user_prompt}],
|
||||
"max_tokens": 400,
|
||||
"temperature": 0.7
|
||||
}
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
if response.status_code != 200: raise UserError(f"Claude API Hatası: {response.text}")
|
||||
generated_text = response.json().get('content', [{}])[0].get('text', '')
|
||||
|
||||
elif self.ai_provider == 'grok':
|
||||
api_key = self.env['ir.config_parameter'].sudo().get_param('mymach_social.grok_api_key')
|
||||
if not api_key: raise UserError("Grok API Anahtarı eksik!")
|
||||
|
||||
url = "https://api.x.ai/v1/chat/completions"
|
||||
headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {api_key}'}
|
||||
payload = {
|
||||
"model": "grok-2",
|
||||
"messages": [
|
||||
{"role": "system", "content": system_instruction},
|
||||
{"role": "user", "content": user_prompt}
|
||||
],
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 400
|
||||
}
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
if response.status_code != 200: raise UserError(f"Grok API Hatası: {response.text}")
|
||||
generated_text = response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
|
||||
|
||||
if not generated_text:
|
||||
raise UserError(f"{self.ai_provider} boş bir yanıt döndürdü.")
|
||||
generated_text = generated_text.strip()
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(f"AI API Error: {str(e)}")
|
||||
raise UserError(f"AI ile iletişim kurulamadı: {str(e)}")
|
||||
|
||||
# --- 2. Generate Image (Free Stable Diffusion via Pollinations) ---
|
||||
media_record = False
|
||||
if self.generate_image:
|
||||
import urllib.parse
|
||||
import base64
|
||||
import requests
|
||||
|
||||
img_prompt = self.image_prompt if self.image_prompt else self.prompt
|
||||
encoded_prompt = urllib.parse.quote(img_prompt)
|
||||
|
||||
pollinations_url = f"https://image.pollinations.ai/prompt/{encoded_prompt}?width=1024&height=1024&nologo=true"
|
||||
|
||||
try:
|
||||
img_data = requests.get(pollinations_url, timeout=30).content
|
||||
encoded_image = base64.b64encode(img_data)
|
||||
|
||||
media_record = self.env['ir.attachment'].create({
|
||||
'name': f"AI_Generated_Image.png",
|
||||
'datas': encoded_image,
|
||||
'res_model': 'mymach.social.post',
|
||||
'res_id': self.post_id.id,
|
||||
'type': 'binary',
|
||||
})
|
||||
except Exception as img_e:
|
||||
_logger.error(f"Stable Diffusion Image Error: {str(img_e)}")
|
||||
raise UserError(f"Ücretsiz görsel üretilirken hata oluştu: {str(img_e)}")
|
||||
|
||||
# --- 3. Update the post ---
|
||||
update_vals = {}
|
||||
if generated_text:
|
||||
update_vals['content'] = generated_text
|
||||
if media_record:
|
||||
update_vals['media_ids'] = [(4, media_record.id)]
|
||||
|
||||
if update_vals:
|
||||
self.post_id.write(update_vals)
|
||||
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_social_ai_wizard_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.ai.wizard.form</field>
|
||||
<field name="model">mymach.social.ai.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Yapay Zeka İçerik Üreticisi">
|
||||
<group>
|
||||
<field name="post_id" invisible="1"/>
|
||||
<field name="ai_provider"/>
|
||||
<field name="prompt" invisible="ai_provider == 'none'"/>
|
||||
<field name="tone" invisible="ai_provider == 'none'"/>
|
||||
<field name="include_hashtags" invisible="ai_provider == 'none'"/>
|
||||
</group>
|
||||
<group string="Görsel Üretimi (Ücretsiz Yapay Zeka)">
|
||||
<field name="generate_image" widget="boolean_toggle"/>
|
||||
<field name="image_prompt" invisible="generate_image == False"/>
|
||||
<div class="alert alert-info" role="alert" invisible="not generate_image">
|
||||
Görseller <b>Ücretsiz (Stable Diffusion)</b> altyapısı ile üretilecektir. Herhangi bir API anahtarı veya ek bakiye gerekmez!
|
||||
</div>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_generate_content" string="Üret (Generate)" type="object" class="btn-primary"/>
|
||||
<button string="İptal" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_social_ai_wizard" model="ir.actions.act_window">
|
||||
<field name="name">AI ile İçerik Üret</field>
|
||||
<field name="res_model">mymach.social.ai.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- AI Image Wizard -->
|
||||
<record id="view_social_ai_image_wizard_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.ai.image.wizard.form</field>
|
||||
<field name="model">mymach.social.ai.image.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Yapay Zeka Görsel Üreticisi">
|
||||
<group>
|
||||
<field name="post_id" invisible="1"/>
|
||||
<field name="image_prompt"/>
|
||||
</group>
|
||||
<div class="alert alert-info" role="alert">
|
||||
Görseller <b>Ücretsiz (Stable Diffusion)</b> altyapısı ile üretilecektir. Herhangi bir API anahtarı veya ek bakiye gerekmez!
|
||||
</div>
|
||||
<footer>
|
||||
<button name="action_generate_image" string="Görsel Üret" type="object" class="btn-primary"/>
|
||||
<button string="İptal" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- AI Reply Wizard -->
|
||||
<record id="view_social_ai_reply_wizard_form" model="ir.ui.view">
|
||||
<field name="name">mymach.social.ai.reply.wizard.form</field>
|
||||
<field name="model">mymach.social.ai.reply.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="AI Yanıt Asistanı">
|
||||
<group>
|
||||
<field name="stream_post_id" invisible="1"/>
|
||||
<field name="ai_provider"/>
|
||||
<field name="customer_message" readonly="1"/>
|
||||
<field name="suggested_reply"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_generate_reply" string="AI Yanıt Üret" type="object" class="btn-info" invisible="suggested_reply != False"/>
|
||||
<button name="action_send_reply" string="Yanıtı Gönder" type="object" class="btn-success" invisible="not suggested_reply"/>
|
||||
<button string="Kapat" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user