From cbd55b307329a8b75bc5adf14ccf7c06453a33ff Mon Sep 17 00:00:00 2001 From: jopster Date: Fri, 17 May 2024 13:55:26 +0200 Subject: [PATCH] milestone commit --- __manifest__.py | 6 +- models/__pycache__/dss.cpython-311.pyc | Bin 66214 -> 88376 bytes models/dss.py | 511 +++++++++++++++++++------ views/dss_ads.xml | 24 +- views/dss_contracts.xml | 153 +++++--- views/dss_mediafiles.xml | 7 +- views/dss_projects.xml | 3 +- views/mainsystem_view.xml | 56 +-- views/menu.xml | 9 +- 9 files changed, 514 insertions(+), 255 deletions(-) diff --git a/__manifest__.py b/__manifest__.py index 6c4b2e5..05a87f1 100755 --- a/__manifest__.py +++ b/__manifest__.py @@ -13,16 +13,18 @@ 'data': [ 'security/groups.xml', 'security/ir.model.access.csv', - 'views/menu.xml', + 'views/dss_contracts.xml', 'views/dss_projectstate.xml', 'views/dss_systemtypen.xml', + 'views/dss_mediarelations.xml', + 'views/dss_advertisementfields.xml', 'views/dss_projects.xml', 'views/dss_ads.xml', - 'views/dss_contracts.xml', 'views/dss_mediafiles.xml', 'views/dss_addstructures.xml', 'views/dss_texts.xml', 'views/mainsystem_view.xml', + 'views/menu.xml', 'views/company_view.xml', ], 'demo': [], diff --git a/models/__pycache__/dss.cpython-311.pyc b/models/__pycache__/dss.cpython-311.pyc index 2249ac00766507c665ae8f7bb98b53aaba194584..922504dcd4f63fb812a6a1d04348fa83d52d28ce 100644 GIT binary patch literal 88376 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 er+1QFt2QdgqD!*q2z_DCW%lmMr%TK1JLD+< diff --git a/models/dss.py b/models/dss.py index 375e79b..2160b0d 100755 --- a/models/dss.py +++ b/models/dss.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -* import ast import json @@ -12,10 +12,12 @@ import easywebdav import os import os.path + from odoo import api, fields, models, _ from odoo import tools from odoo.exceptions import ValidationError from datetime import date +from pyffmpeg import FFmpeg _logger = logging.getLogger(__name__) @@ -28,6 +30,15 @@ def _generate_preview_from_binary(self, videofile_file): return base64.b64encode(out) +def _generate_preview_from_binary_2(self, videofile_file): + ff=FFmpeg() + p = subprocess.Popen(cmd,stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out,err = p.communicate(videofile_file) + if p.returncode != 0: + pass + + return base64.b64encode(out) + class dsscontracts(models.Model): @api.model @@ -53,21 +64,33 @@ class dsscontracts(models.Model): # ds = return ds + def _default_get_ads_last_ad(self): + _logger.info('finding Standard ad '+self.id) + ds = self.env['dss.ads'].search([('contract','=',self.id),('ad_is_lastpos','=',True)],limit=1) + if not ds: + ds = self.env['dss.ads'].search([('contract','=',self.id)],limit=1) + return ds _name = "dss.contracts" _description = "DigitalSignage Vertraege" _rec_name = "contract_auto_name" _inherit = ['mail.thread','mail.activity.mixin'] uuid = fields.Char(default=lambda self: self._default_uuid(), required=True, readonly=True, copy=False, string='UUID') + cruuid = fields.Char(related='uuid') contract_id = fields.Char("Kundennummer",store=True,tracking=True) contract_name = fields.Char('Kurzbezeichnung', required=True,tracking=True) contract_state = fields.Many2one('dss.contractstate',group_expand='_read_group_stage_ids',tracking=True) contract_state_order = fields.Integer(related='contract_state.order',store=True) contract_auto_id = fields.Char("Kundennummer",tracking=True) contract_auto_name = fields.Char('Vertragskennug',tracking=True) + + contract_remark = fields.Html('Vertragshinweise',tracking=True) + project = fields.Many2one('dss.projects' , string='Project', store=True,tracking=True) project_id = fields.Integer(related='project.projectid', string='Project ID') projectIid = fields.Integer('Project IID',tracking=True) + project_ad_structure = fields.Many2one(related='project.grundsystem_default_adstructure', string='Aufbau') + client = fields.Many2one('res.partner',domain="['&',('dsspartner','=',True),('dsspartner_werbung','=',True)]",tracking=True) client_id = fields.Char("Kundenid",tracking=True) client_uuid = fields.Char(related="client.dss_uuid") @@ -87,7 +110,14 @@ class dsscontracts(models.Model): client_other_projects = fields.Many2many('dss.projects',string='Weitere Projekte',tracking=True) - werbe_feld_selected = fields.Many2many('dss.advertisefields',string='Werbefelder',tracking=True) + werbe_feld_selected = fields.One2many('dss.advertisefields','contract',string='Werbefelder',tracking=True) + + need_media = fields.Many2many('dss.mediarelations','mediarelations_contract_rel','contract_id','mediarelations_id',string='benötigte Medien') + last_media = fields.One2many('dss.mediarelations','contract',string='Medien') +# last_media = fields.Many2many('dss.mediarelations','mediarelations_ad_relations','contract_id','mediarelations_id',string='Medien') +# need_media = fields.Many2many('dss.mediarelations','contract',string='Medien') +# need_media_computed = fields.One2many('dss.mediarelations','field',compute='_get_media_list') +# need_media_computed = fields.One2many('dss.mediarelations','uuid',domain="[('field','in',werbe_feld_selected)]") main_runtime = fields.Integer('Gesamtlaufzeit',tracking=True) split_runtime_count = fields.Integer('Laufzeit Teilungen',tracking=True) @@ -107,6 +137,17 @@ class dsscontracts(models.Model): base_ad = fields.Many2one('dss.ads',tracking=True) ads = fields.One2many('dss.ads','contract',tracking=True) + ads_last_ad = fields.Many2one('dss.ads', help="letzte Werbekampagne",computed='_default_get_ads_last_ad',store=True) + ads_last_state = fields.Many2one(related='ads_last_ad.ad_state', help="Status des letzten Werbekampagnen Eintrags") + ads_last_state_color = fields.Char(related='ads_last_ad.ad_state_color') + ads_last_state_text = fields.Char(related='ads_last_ad.ad_state_text') + ads_last_work_state = fields.Many2one(related='ads_last_ad.work_state', help="Arbeitsstatus des letzten Werbekampagnen Eintrags") + ads_last_work_state_color = fields.Char(related='ads_last_ad.work_state_color') + ads_last_work_state_text = fields.Char(related='ads_last_ad.work_state_text') + ads_last_todo_state = fields.Many2one(related='ads_last_ad.todo_state', help="Aufgabenstatus des letzten Werbekampagnen Eintrags") + ads_last_todo_state_until = fields.Date(related='ads_last_ad.todo_state_until') + ads_last_todo_state_color = fields.Char(related='ads_last_ad.todo_state_color') + ads_last_todo_state_text = fields.Char(related='ads_last_ad.todo_state_text') vnnox_zugang_erstellt = fields.Boolean('Vnnox Zugang ?',tracking=True) vnnox_zugang_username = fields.Char('Vnnox Username',tracking=True) @@ -133,18 +174,35 @@ class dsscontracts(models.Model): info_partner_changes = fields.Boolean('Benachrichtigen bei Partneränderungen',tracking=True) info_partner = fields.Many2one('res.partner','Benachrichtigung an : ',tracking=True) - work_state = fields.Many2one('dss.workstate',default=_default_work_state,tracking=True) - work_state_color = fields.Char(related='work_state.color') - work_state_text = fields.Char(related='work_state.statusname') +# work_state = fields.Many2one('dss.workstate',default=_default_work_state,tracking=True) +# work_state_color = fields.Char(related='work_state.color') +# work_state_text = fields.Char(related='work_state.statusname') work_state_info = fields.Char('Zusatzinfo',tracking=True) - todo_state = fields.Many2one('dss.todostate',default=_default_todo_state,tracking=True) - todo_state_color = fields.Char(related='todo_state.color') - todo_state_text = fields.Char(related='todo_state.statusname') - todo_state_info = fields.Char('Zusatzinfo',tracking=True) - todo_state_until = fields.Date('Abarbeiten bis',tracking=True) +# todo_state = fields.Many2one('dss.todostate',default=_default_todo_state,tracking=True) +# todo_state_color = fields.Char(related='todo_state.color') +# todo_state_text = fields.Char(related='todo_state.statusname') +# todo_state_info = fields.Char('Zusatzinfo',tracking=True) +# todo_state_until = fields.Date('Abarbeiten bis',tracking=True) - cloud_contract_directory = fields.Char('Cloud Kunden Ordner',tracking=True) + cloudlink = fields.Char('Cloud Verzeichnis',help='Verzeichnis für das Project innerhalb des Projekt Ordners') + + @api.onchange('werbe_feld_selected') + def _onchange_werbe_feld_selected(self) : + for record in self : +# self.write({'need_media': [(5,0,0)]}) + medias_ids = [] + for feld in record.werbe_feld_selected: + _logger.info("Feld : %s %s" % (record._origin.id,feld._origin.id)) + medias = self.env['dss.mediarelations'].search([('field','=',feld._origin.id)]) + for med in medias: + med.contract=record._origin.id + medias_ids.append(med.id) + self.write({'need_media': [(6,0,medias_ids)]}) + _logger.info("Media : "+str(record.need_media)) +# mrelobj.write({'field': feldid }) +# mrelobj.write({'project': record.project.id}) + @api.constrains('client_id') def _check_client_id(self) : @@ -195,7 +253,6 @@ class dsscontracts(models.Model): resstr = 'nicht ermittelbar' _logger.debug(resstr) self.contract_auto_name = resstr - # @api.model # def create(self,vals): @@ -205,10 +262,12 @@ class dsscontracts(models.Model): # contract=super().create(vals) # return contract + @api.model def _default_uuid(self): return str(uuid.uuid4()) + @api.model def _getdefwscolor(self): return str('#ffffff') @@ -236,41 +295,115 @@ class dsscontracts(models.Model): return action def tokampagne(self): - action = self.env['ir.actions.act_window'].with_context({'default_contractid': self.id})._for_xml_id('DigitalSignage.action_dss_ads_view') - context = action['context'] + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+' - prüfe Letzte Aktuelle Kampagne') +# action = self.env['ir.actions.act_window'].with_context({'default_contractid': self.id})._for_xml_id('DigitalSignage.action_dss_ads_view') +# context = action['context'] + ds = self.env['dss.ads'].search([('contract','=',self.id),('ad_is_lastpos','=',True)],limit=1) + if not ds: + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+' - keine letzte Kampagne') + ds = self.env['dss.ads'].search([('contract','=',self.id)],limit=1) + else: + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'A_'+str(ds.id)+' - Kampagne gefunden ') + self.ads_last_ad = ds +# prüfen ob LetzteKampagne medien hat + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'A_'+str(ds.id)+' - Suche evtl Medien der Kampagne ') + medias = self.env['dss.mediarelations'].search(['&',('contract','=',self.id),('ad','=',ds.id)]) + if medias: + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'A_'+str(ds.id)+' - Medien gefunden '+str(medias.ids)) + self.write({'last_media': [(6,0,medias.ids)]}) + else: + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'A_'+str(ds.id)+' - Keine Medien gefunden ') + +# Medien erzeugen wenn nicht vorhanden kampagne=self.env['dss.ads'].search([('contract','=',self.id)],limit=1) - if not kampagne : - defadstate = self.env['dss.adstate'].search([('default','=',True)],limit=1).id - if not defadstate : - defadstate == 1 - kampagne = self.env['dss.ads'].create({'contract': self.id, 'project': self.project.id, 'adname': 'WK '+self.contract_auto_name,'adtype':'MAIN','ad_state':defadstate}) - kampagne.parent_ad = kampagne.id + _logger.info('Click auf Werbekampagne : '+str(self.id)+' 3') + if not kampagne: + _logger.info('Click auf Werbekampagne : '+str(self.id)+' kein kampagne') + defadstate = self.env['dss.adstate'].search([('func','=','STD')],limit=1) + if not defadstate : + _logger.info('Click auf Werbekampagne : '+str(self.id)+' kein defstate') + defadstateid = 1 + else: + defadstateid = defadstate.id + kampagne = self.env['dss.ads'].create({'contract': self.id, 'project': self.project.id, 'adname': 'WK '+self.contract_auto_name,'adtype':'MAIN','ad_state':defadstateid}) + self.ads_last_ad = kampagne.id + _logger.info('Click auf Werbekampagne : '+str(self.id)+' Kampagne erstellt '+str(kampagne.id)) + kampagne.parent_ad = kampagne.id + mediaids = [] + for singlemedi in self.need_media: + if not singlemedi: + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(kampagne.id)+' Kein Medium in NEED_MEDIA') + else: + newmedi=singlemedi.copy({'contract':self.id,'ad':kampagne.id}) + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(kampagne.id)+' setze Vertrag für Medium : '+str(newmedi.contract)+'/'+str(newmedi.ad)) + mediaids.append(newmedi.id) + kampagne.write({'need_media': [(6,0,mediaids)]}) + _logger.info('Click auf Werbekampagne : '+str(self.id)+'/'+str(kampagne.id)+' setze Media '+str(kampagne.need_media)) + else: + _logger.info('Click auf Werbekampagne : '+str(self.id)+' mind. 1 Kampagne vorhanden '+str(kampagne.id)) + _logger.info('Prüfe Medien : C_'+str(self.id)) + kampagnen=self.env['dss.ads'].search([('contract','=',self.id)]) + _logger.info('Prüfe Medien : C_'+str(self.id)+' alle Kamp : '+str(kampagnen)) + for kamp in kampagnen: + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(kamp.id)) + if kamp: + mediaids = [] + medias = self.env['dss.mediarelations'].search([('ad','=',kamp.id)]) + if not medias: + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(kamp.id)+' keine Medien gefunden !') + for singlemedi in self.need_media: + if not singlemedi: + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(kamp.id)+' Kein Medium in NEED_MEDIA') + else: + newmedi=singlemedi.copy({'ad':kamp.id}) + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(kamp.id)+' Kopiere Medium : '+str(singlemedi)+' -> '+str(newmedi)) + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(kamp.id)+' Medien evtl erstellt !') + medias = self.env['dss.mediarelations'].search([('ad','=',kamp.id)]) + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'K_'+str(kamp.id)+' neue Media '+str(mediaids)) + kamp.write({'need_media': [(6,0,medias.ids)]}) +# self.write({'need_media': [(5,0,0)]}) +# _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'K_'+str(kamp.id)+' setze Media '+str(kamp.need_media)) + else: + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(kamp.id)+' Medien gefunden !') + medias = self.env['dss.mediarelations'].search([('ad','=',kamp.id)]) +# mediaids.append(medias.ids) + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'K_'+str(kamp.id)+' gefundene Media '+str(mediaids)) + kampagne=self.env['dss.ads'].browse(kamp.id) + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'K_'+str(kamp.id)+' gefundene Media '+str(mediaids)+' Kampagne '+str(kampagne)) + if kampagne.need_media != medias: + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'K_'+str(kamp.id)+' Media unterschiede - setze Media '+str(mediaids)+'/'+str(kampagne.need_media)) + else: + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'K_'+str(kamp.id)+' Media gleich '+str(kampagne.need_media)) + else: + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(kamp.id)+' Keine Kampagne gefunden !') +# _logger.info('Click auf Werbekampagne : '+str(self.id)+' 4 '+str(kampagne.id)) return { 'type': 'ir.actions.act_window', 'view_type':'form', 'view_mode':'form,tree', 'res_model':'dss.ads', 'target':'current', + 'context':'{"kampagne_id":'+str(kampagne.id)+',"show_project_update":True}', 'context':'', 'res_id':kampagne.id, 'display_name' : 'Änderung zur Werbekampagne '+kampagne.parent_ad.adname, - 'domain':'[("id","=","context[kampagne.id]")]' + 'domain':'[("id","=","context[kampagne_id]")]' } - @api.model - def pyaction_view_contract(self): - action = self.env['ir.actions.act_window'].with_context({'default_contractid': self.id})._for_xml_id('DigitalSignage.act_dss_project_view_contract') - action['display_name'] = self.contract_name -# action['domain'] = '[["projectid","=","4"]]' -# context = action['context'].replace('', str(self.id)) -# context = ast.literal_eval(context) -# context.update({ -# 'create': self.active, -# 'active_test': self.active -# }) -# action['context'] = context - return action + def pyaction_view_contract(self): + view = self.env.ref('DigitalSignage.dss_main_contracts_form') + _logger.debug('Click auf Vertrag : '+str(self.id)) + return { + 'type': 'ir.actions.act_window', + 'view_mode': 'form' , + 'view_id': view.id, + 'res_model': 'dss.contracts' , + 'target': 'current' , + 'display_name' : self.contract_name, + 'views':[(view.id,'form')], + 'res_id': self.id , + } def pyaction_new_contract_kanban(self): action = self.env['ir.actions.act_window'].with_context({'default_contractid': self.id})._for_xml_id('DigitalSignage.action_dss_project_new_contract_kanban') @@ -311,6 +444,7 @@ class dssmain(models.Model): systemname = fields.Many2one('dss.systems',tracking=True) grundsystemname = fields.Many2one('dss.systemtypen',tracking=True) grundsystemicon = fields.Image(related='grundsystemname.icon',tracking=True) + grundsystem_default_adstructure = fields.Many2one(related='grundsystemname.default_adstructure',tracking=True) grundsystemicon5050 = fields.Image(related='grundsystemname.icon_5050') vertragsschreiber = fields.Many2one('res.partner',domain="['&',('dsspartner','=',True),('dsspartner_vertrieb','=',True)]",tracking=True) standortpartner = fields.Many2one('res.partner',domain="['&',('dsspartner','=',True),('dsspartner_standort','=',True)]",tracking=True) @@ -320,23 +454,37 @@ class dssmain(models.Model): errichtet_am = fields.Datetime('Errichtungstag',tracking=True) standort_inout = fields.Selection([('indoor','im Gebäude'), ('outdoor','Aussenbereich'), ('semiindoor','Überdachter Aussenbereich'),('Divers','Andere Art')],tracking=True); - cloud_main_directory = fields.Char('Cloud Projekt Ordner',tracking=True) + cloudlink = fields.Char('Cloud Verzeichnis',help='Verzeichnis für das Project innerhalb des Cloud Struktur') @api.model def _default_uuid(self): return str(uuid.uuid4()) def pyaction_view_contracts(self): - action = self.env['ir.actions.act_window'].with_context({'default_project': self.id})._for_xml_id('DigitalSignage.action_dss_project_contracts') - action['display_name'] = self.projektname - context = action['context'].replace('active_id', str(self.id)) +# action = self.env['ir.actions.act_window'].with_context({'default_project': self.id})._for_xml_id('DigitalSignage.action_dss_project_contracts') +# action['display_name'] = self.projektname +# context = action['context'].replace('active_id', str(self.id)) +# domain = action['domain'].replace('active_id', str(self.id)) +# _logger.info('Vertraege fuer : '+str(self.id)) + return { + 'type': 'ir.actions.act_window', + 'view_type':'kanban', + 'view_mode':'kanban,tree', + 'res_model':'dss.contracts', + 'target':'current', + 'context':'{"default_project":'+str(self.id)+',"show_project_update":True}', + 'res_id':self.id, + 'display_name' : self.projektname, + 'domain':'[("project","=",'+str(self.id)+')]' + } # context = ast.literal_eval(context) # context = "{ # 'create': self.active, # 'active_test': self.active # } - action['context'] = context - return action +# action['context'] = context +# action['domain'] = domain +# return action @@ -566,7 +714,7 @@ class dssadstatus(models.Model): # uuid = fields.Char('UUID', required=True, translate=True) statusname = fields.Char('Statusname', required=True) color = fields.Char(string='Color Index') - default = fields.Boolean('Standardwert ?') + func = fields.Selection([('STD','Standard'), ('FIN','Endzustand'), ('WOR','Arbeitszustand')]) icon = fields.Image() order = fields.Integer('Reihenfolge') @@ -619,7 +767,7 @@ class dsstodostatus(models.Model): uuid = fields.Char(default=lambda self: self._default_uuid(), required=True, readonly=True, copy=False, string='UUID') # uuid = fields.Char('UUID', required=True, translate=True) statusname = fields.Char('Statusname', required=True) - statusnr = fields.Integer('Litenpostion', required=True) + statusnr = fields.Integer(default=lambda self: self._default_statusnr(),string='Listenpostion', required=True) color = fields.Char(string='Color Index') icon = fields.Image() @@ -627,20 +775,74 @@ class dsstodostatus(models.Model): def _default_uuid(self): return str(uuid.uuid4()) + @api.model + def _default_statusnr(self): + return str(1) + class dssadvertisefields(models.Model): + + @api.model + def create(self,vals): + result = super().create(vals) + result.issaved = True + return result + _name = "dss.advertisefields" _description = "DigitalSignage Werbefelder" _inherit = ['mail.thread','mail.activity.mixin'] _rec_name = "feldname" # _inherit = ['mail.thread', 'mail.activity.mixin'] uuid = fields.Char(default=lambda self: self._default_uuid(), required=True, readonly=True, copy=False, string='UUID') + date_create = fields.Date('Erstellungsdatum',default=lambda self: self._default_create_date()) + date_lastedit = fields.Date('Änderungsdatum') + user_create = fields.Char('Erstellungsuser',default=lambda self: self._default_create_user()) + user_lastedit = fields.Char('Änderungsuser') + issaved = fields.Boolean('ist gespeichert') # uuid = fields.Char('UUID', required=True, translate=True) feldname = fields.Char('Feldname', required=True) project = fields.Many2one('dss.projects' , string='Project', store=True) project_id = fields.Integer(related='project.projectid', string='Project ID') + contract = fields.Many2one('dss.contracts' , string='Vertrag', store=True) color_used = fields.Char(string='Color Index') color_unused = fields.Char(string='Color Index') - icon = fields.Image() + mediastructure = fields.Many2one('dss.adstructures',string='Feldaufbau',tracking=True) + mediastructure_medias = fields.Many2many(related='mediastructure.medias',string='Inhalt') + mediarelations = fields.One2many('dss.mediarelations','relname',tracking=True) + + def _default_create_date(self): + return str(date.today()) + + def _default_create_user(self): + self.issaved = True + return str(self.env.user.name) + + + @api.onchange('mediastructure') + def _onchange_mediastructure_id(self): + restr = 'keine' + for record in self : + resstr = record.mediastructure.uuid + mtyp = self.env['dss.adstructures'].search([("uuid","=",str(record.mediastructure.uuid))]) + _logger.info(resstr) + if mtyp: + resstr = mtyp.structurename + _logger.info(resstr + ' uuid : '+record.mediastructure.uuid) + foundused = self.env['dss.mediarelations'].search_count(["&",('field_uuid','=',record.uuid),'|',('secured_ro','=',True),('used_ro','=',True)]) + if foundused == 0 : + self.env['dss.mediarelations'].search([('field_uuid','=',record.uuid)]).unlink() + for media in mtyp.medias : + resstr = media.medianame + _logger.info('Erstellung : '+str(self._origin.id)+'/'+str(record.id)+'('+str(record._origin.id)+')/'+str(media.id)+'/'+str(record.project.id)+' '+str(restr)) + mrelobj = self.env['dss.mediarelations'].create({'field':record.id,'relname':resstr,'mediatype': media.id,'field_uuid':record.uuid,'project':record.project.id}) + feldid = self._origin.id + mrelobj.write({'field': feldid }) + mrelobj.write({'project': record.project.id}) + _logger.info('Erzeugt : '+str(mrelobj)+' - '+str(mrelobj.project)+' - '+str(mrelobj.field)) + else : + _logger.info('change - Canceled !') + raise ValidationError(_("Datensatz kann nicht gewechselt werden ! Es sind benutzt Medien in der Kampagne ! Bitte erst diese freigeben !")) + self.date_lastedit = str(date.today()) + self.mediastructure = mtyp @api.model def _default_uuid(self): @@ -692,10 +894,12 @@ class dssmediatypes(models.Model): medianame = fields.Char('Medien Name', required=True) mediatype = fields.Selection([('IMG_J','Bild'),('VID_4','MP4 Video'),('FIL_X','belieb. Datei')]) description = fields.Text('Medien Beschreibung') - cloudlink = fields.Char('Cloud Urverzeichnis') + cloudlink = fields.Char('Cloud Verzeichnis',help='Verzeichnis für diese Datei innerhalb des Vertrags-Ordners') + filepartname = fields.Char('Part.Dateiname',help='Teil des Dateinamens. Wird ergaenzt durch KampagnenID und MedienID. Beispiel : PNAME_34_544.jpg') maxsize_kb = fields.Integer('Maximale Größe KB') maxsize_w = fields.Integer('Maximale Pixel W') maxsize_h = fields.Integer('Maximale Pixel H') + standard_image = fields.Image() @api.model def _default_uuid(self): @@ -703,55 +907,52 @@ class dssmediatypes(models.Model): class dssmediarelations(models.Model): - def _generate_preview_from_binary(self, videofile_file): - cmd = ['ffmpeg', '-i','-','-ss','00:00:01','-vframes','1','-f','image2','-'] - p = subprocess.Popen(cmd,stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out,err = p.communicate(videofile_file) - if p.returncode != 0: - pass - - return base64.b64encode(out) - - def _generate_preview_and_save_from_binary(self, videofile_file, videofile_file_name:str): - cmd = ['ffmpeg', '-i','-','-ss','00:00:01','-vframes','1','-f','image2','-'] - p = subprocess.Popen(cmd,stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out,err = p.communicate(videofile_file) - if p.returncode != 0: - _logger.info(videofile_file_name) - fileobj = open(videofile_file_name,"xb") - fileobj.write(out) - fileobj.close() - pass - - return base64.b64encode(out) - - _name = "dss.mediarelations" _description = "DigitalSignage Kampagne-Medien-Zuordnung" - _inherit = ['mail.thread','mail.activity.mixin'] + _inherit = [] _rec_name = "relname" # _inherit = ['mail.thread', 'mail.activity.mixin'] uuid = fields.Char(default=lambda self: self._default_uuid(), required=True, readonly=True, copy=False, string='UUID') # uuid = fields.Char('UUID', required=True, translate=True) - kampagne = fields.Many2one('dss.ads',string='Kampagne') + field = fields.Many2one('dss.advertisefields',string='Feld') + contract = fields.Many2one(related='field.contract',string='Vertrag') + ad = fields.Many2one('dss.ads',string='Werbekampagne') + project = fields.Many2one('dss.projects',string='Werbeprojekt') mediatype = fields.Many2one('dss.mediatypes',string='Medientyp') mediatype_type = fields.Selection(related='mediatype.mediatype',string='Medientyp') + mediatype_name = fields.Char(related='mediatype.medianame',string='Medienname') relname = fields.Char('Relationsname') - kampagnen_uuid = fields.Char('Kampagne') + field_uuid = fields.Char('Feld') mediatype_uuid = fields.Char('Medientyp') mediafile = fields.Binary('Datei',attachment=True) mediafile_attachment = fields.Many2one('ir.attachment',string='Datei') mediafile_file = fields.Char('Dateiname') - mediafile_preview = fields.Binary('Datei Vorschau',compute='_compute_media_preview') secured_ro = fields.Boolean('Gesperrt ro') used_ro = fields.Boolean('Benutzt ro') +# @api.onchange('field') +# def _onchange_field(self): +# for record in self: +# if record.field='': +# record.contract='' +# else: +# record.contrect=record.field_contract + + def unlink(self): + _logger.info('unlinking '+str(self)) + for record in self: + _logger.info('unlinking '+str(record)+' '+str(record.ad)) + if record.ad != False: + super(dssmediarelations,record).unlink() + else: + _logger.info('not unlinking '+str(record)+' '+str(record.ad)) + @api.onchange('mediafile') def _onchange_mediafile(self): restr = 'keine' for record in self : resstr = record.uuid - _logger.info(record.mediafile) + _logger.info(record.mediafile_file) if record.mediafile != False : _logger.info('Generating File '+resstr) if os.path.isfile(record.mediafile_file) : @@ -760,28 +961,34 @@ class dssmediarelations(models.Model): fileobj.write(base64.decodebytes(record.mediafile)) fileobj.close() _logger.info(resstr+' File generated') - _logger.info(' Projekt : '+record.kampagne.contract.project.uuid) + _logger.info(' Projekt : P_'+str(record.project)+'C_'+str(record.contract)+'A_'+str(record.ad)+' F : '+restr+' created ') _logger.info(' Keine Datei ') return + def dload(self): + for record in self : + _logger.info('Download pressed : '+str(record)) + + return {} + @api.model def _default_uuid(self): return str(uuid.uuid4()) - @api.depends('mediafile_file') - def _compute_media_preview(self): - for rec in self: - if rec.mediafile != False: - _logger.info('compute image for '+rec.uuid) +# @api.depends('mediafile_file') +# def _compute_media_preview(self): +# for rec in self: +# if rec.mediafile != False: +# _logger.info('compute image for '+rec.uuid) # rec.mediafile_preview = self._generate_preview_from_binary(base64.decodebytes(rec.mediafile)) - rec.mediafile_preview = self._generate_preview_and_save_from_binary(base64.decodebytes(rec.mediafile),rec.mediafile_file+'_prv') +# rec.mediafile_preview = self._generate_preview_and_save_from_binary(base64.decodebytes(rec.mediafile),rec.mediafile_file+'_prv') # if rec.mediafile != False: # rec.mediafile_preview = self._generate_preview_from_binary(base64.decodebytes(rec.mediafile)) # rec.mediafile_preview = None - else: - _logger.info('alternative compute '+rec.uuid) - rec.mediafile_preview = rec.mediafile - return +# else: +# _logger.info('alternative compute '+rec.uuid) +# rec.mediafile_preview = rec.mediafile +# return class dssadstructures(models.Model): _name = "dss.adstructures" @@ -846,16 +1053,42 @@ class dsscontractads(models.Model): @api.model def create(self,vals): result = super().create(vals) - self['date_create'] = date.today() - self['user_create'] = self.env.user.name + resstr = result.uuid + _logger.info(resstr) + for n_record in result: + resstr = n_record.uuid + _logger.info(resstr) + n_record.date_create = date.today() + n_record.user_create = self.env.user.name + allads = n_record.contract.ads + _logger.info(allads) + if allads != False: + for ad in allads: + ad.ad_is_lastpos = False + n_record.ad_is_lastpos = True + contract = n_record.contract + contract.ads_last_ad = n_record return result + @api.depends('need_media') + def _getmedialist(self): + mlist = [] + mlist = self.env['dss.mediarelations'].search([('ad','=',self.id)]) + if mlist != False: + _logger.info('AD Need_Medias A_'+str(self)+' - Computed Medias '+str(mlist)) + self.need_media = mlist.ids + else: + self.need_media = False + _name = "dss.ads" _description = "DigitalSignage Werbekampagnen Phasen" - _inherit = ['mail.thread','mail.activity.mixin'] +# _inherit = ['mail.thread','mail.activity.mixin'] + _inherit = ['mail.activity.mixin'] +#'mail.thread','mail.activity.mixin' _rec_name = "adname" # _inherit = ['mail.thread', 'mail.activity.mixin'] uuid = fields.Char(default=lambda self: self._default_uuid(), required=True, readonly=True, copy=False, string='UUID') + aduuid = fields.Char(related='uuid') date_create = fields.Date('Erstellungsdatum',default=lambda self: self._default_create_date()) date_lastedit = fields.Date('Änderungsdatum') @@ -865,10 +1098,12 @@ class dsscontractads(models.Model): adname = fields.Char('Kampagnenname', required=True,track_visibility='onchange',tracking=True) adtype = fields.Selection([('MAIN','Ersteinrichtung'),('KCHN','Änderung durch Kunde'),('LCHN','Änderung durch Logumedia'),('SONS','Sonstige Änderung')],track_visibility='onchange',tracking=True) adpos = fields.Integer('Reihenfolge',default=lambda self: self._default_adpos(),track_visibility='onchange',tracking=True) + ad_is_lastpos = fields.Boolean('Letzte Aktion') contract = fields.Many2one('dss.contracts',store=True,track_visibility='onchange',tracking=True) contract_id = fields.Char(related='contract.contract_id') contract_name = fields.Char(related='contract.contract_name') + contract_need_media = fields.Many2many('contract.need_media',tracking=True) project = fields.Many2one('dss.projects' , string='Project', store=True,track_visibility='onchange',tracking=True) project_id = fields.Integer(related='project.projectid', string='Project ID') @@ -879,6 +1114,9 @@ class dsscontractads(models.Model): amount = fields.Float('Extrakosten') order = fields.Integer('Reihenfolge') +# need_media = fields.Many2many('dss.mediarelations','ad',string='Medien') + need_media = fields.Many2many('dss.mediarelations','mediarelations_ad_relations','ad_id','mediarelations_id',string='Medien') + work_state = fields.Many2one('dss.workstate',default=_default_work_state,tracking=True) work_state_color = fields.Char(related='work_state.color') work_state_text = fields.Char(related='work_state.statusname') @@ -886,6 +1124,8 @@ class dsscontractads(models.Model): ad_state = fields.Many2one('dss.adstate',tracking=True) ad_state_color = fields.Char(related='ad_state.color') + ad_state_text = fields.Char(related='ad_state.statusname') + ad_state_func = fields.Selection(related='ad_state.func') todo_state = fields.Many2one('dss.todostate',default=_default_todo_state,tracking=True) todo_state_color = fields.Char(related='todo_state.color') @@ -893,16 +1133,14 @@ class dsscontractads(models.Model): todo_state_info = fields.Char('Zusatzinfo') todo_state_until = fields.Date('Abarbeiten bis') - mediastructure = fields.Many2one('dss.adstructures',string='Systemaufbau',tracking=True) - mediarelations = fields.One2many('dss.mediarelations','kampagne',tracking=True) cloud_add_directory = fields.Char('Cloud KundenKampagnen Ordner',tracking=True) + @api.model def _default_uuid(self): return str(uuid.uuid4()) - def _default_create_date(self): return str(date.today()) @@ -913,28 +1151,18 @@ class dsscontractads(models.Model): pos = self.env['dss.ads'].search_count([('contract_id','=',self.contract.id)]) + 1 return str(pos) - @api.onchange('mediastructure') - def _onchange_mediastructure_id(self): - restr = 'keine' + + @api.onchange('ad_state') + def _onchange_ad_state(self): for record in self : - resstr = record.mediastructure.uuid - _logger.info(resstr) - mtyp = self.env['dss.adstructures'].search([("uuid","=",str(record.mediastructure.uuid))]) - resstr = mtyp.structurename - _logger.info(resstr) - _logger.info(record.mediastructure.uuid) - foundused = self.env['dss.mediarelations'].search_count(["&",('kampagnen_uuid','=',record.uuid),'|',('secured_ro','=',True),('used_ro','=',True)]) - if foundused == 0 : - self.env['dss.mediarelations'].search([('kampagnen_uuid','=',record.uuid)]).unlink() - for media in mtyp.medias : - resstr = media.medianame - self.env['dss.mediarelations'].create({'kampagne': record.id,'relname':resstr,'mediatype':media.id,'kampagnen_uuid':record.uuid}) - _logger.info(resstr) - else : - _logger.info('change - Canceled !') - raise ValidationError(_("Datensatz kann nicht gewechselt werden ! Es sind benutzt Medien in der Kampagne ! Bitte erst diese freigeben !")) + if record.ad_is_lastpos == True: + contract = record.contract + contract.ads_last_state = record.ad_state +# mtyp = self.env['dss.adstructures'].search([("uuid","=",str(record.mediastructure.uuid))]) +# buildmediarelations(self) self.date_lastedit = str(date.today()) - self.mediastructure = mtyp +# self.mediastructure = mtyp + def pyaction_view_ad_details(self): action = self.env['ir.actions.act_window'].with_context({'default_adid': self.id})._for_xml_id('DigitalSignage.action_dss_ads_view') @@ -997,21 +1225,49 @@ class dsscontractads(models.Model): } def pydonewad(self): - defadstate = self.env['dss.adstate'].search([('default','=',True)],limit=1).id - if not defadstate : - defadstate == 1 - newkampagne = self.env['dss.ads'].create({'contract': self.contract.id, 'project': self.project.id, 'adname': 'AD_'+self.adname,'parent_ad':self.id, 'adtype':'KCHN','ad_state':defadstate }) -# action = self.env['ir.actions.act_window'].with_context({'default_contractid': newkampagne.id})._for_xml_id('DigitalSignage.act_dss_ads_view_contract') -# action['display_name'] = self.contract_name -# action['domain'] = '[["projectid","=","4"]]' -# context = action['context'].replace('', str(self.id)) -# context = ast.literal_eval(context) -# context.update({ -# 'create': self.active, -# 'active_test': self.active -# }) -# action['context'] = context - return { + for n_record in self: + resstr = n_record.uuid + _logger.info(resstr) + allads = n_record.contract.ads + _logger.info(allads) + abort = False + if allads != False: + for ad in allads: + if ad.ad_state.func != 'FIN': + abort = True + if abort == False: + defadstate = self.env['dss.adstate'].search([('func','=','STD')],limit=1).id + if not defadstate : + defadstate = 1 + if self.ad_state.func == 'STD': + parent_id = self.id + else: + parent_id = self.parent_ad.id + newkampagne = self.env['dss.ads'].create({'contract': self.contract.id, 'project': self.project.id, 'adname': 'AD_'+self.contract.contract_auto_name+' '+str(date.today()),'parent_ad':parent_id, 'adtype':'KCHN','ad_state':defadstate }) + _logger.info('C_'+str(self.id)+' Kampagne erzeugt : '+str(newkampagne)) + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(newkampagne.id)) + mediaids = [] + medias = self.env['dss.mediarelations'].search([('ad','=',newkampagne.id)]) + if not medias: + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(newkampagne.id)+' keine Medien gefunden !') + medias = self.env['dss.mediarelations'].search(['&',('contract','=',self.contract.id),('ad','=',False)]) + if not medias: + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(newkampagne.id)+' keine Medien im vertrag gefunden !') + else: + for singlemedi in medias: + if not singlemedi: + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(newkampagne.id)+' Kein Medium') + else: + newmedi=singlemedi.copy({'ad':newkampagne.id}) + mediaids.append(newmedi.id) + _logger.info('Prüfe Medien : C_'+str(self.id)+'K_'+str(newkampagne.id)+' Kopiere Medium : '+str(singlemedi)+' -> '+str(newmedi)) + medias = self.env['dss.mediarelations'].search([('ad','=',newkampagne.id)]) + _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'K_'+str(newkampagne.id)+' neue Media '+str(mediaids)) + newkampagne.write({'need_media': [(6,0,medias.ids)]}) +# self.write({'need_media': [(5,0,0)]}) +# _logger.info('Click auf Werbekampagne : C_'+str(self.id)+'K_'+str(kamp.id)+' setze Media '+str(kamp.need_media)) +# _logger.info('Click auf Werbekampagne : '+str(self.id)+' 4 '+str(kampagne.id)) + return { 'type': 'ir.actions.act_window', 'view_type':'form', 'view_mode':'form,tree', @@ -1021,9 +1277,10 @@ class dsscontractads(models.Model): 'res_id':newkampagne.id, 'display_name' : 'Änderung zur Werbekampagne '+newkampagne.parent_ad.adname, 'domain':'[("id","=","context[newkampagne.id]")]' - } - - return action + } + else: + raise ValidationError(_("Kann neue Aktualisierung erst anlegen wenn alle vorherigen Schritte beendet wurden !")) + def setStandardText(self,tid): text = self.env['dss.texts'].search([('text_id','=',tid)], limit=1) diff --git a/views/dss_ads.xml b/views/dss_ads.xml index 40d3503..6893124 100755 --- a/views/dss_ads.xml +++ b/views/dss_ads.xml @@ -9,7 +9,7 @@
@@ -39,7 +39,6 @@
-
@@ -125,12 +124,12 @@
- +-- - - + + - + @@ -142,8 +141,10 @@ - - + + + + @@ -156,11 +157,11 @@
-
+ @@ -264,6 +265,7 @@
+
diff --git a/views/dss_contracts.xml b/views/dss_contracts.xml index 973c87e..5058b4e 100755 --- a/views/dss_contracts.xml +++ b/views/dss_contracts.xml @@ -6,9 +6,9 @@ ir.actions.act_window dss.contracts kanban,tree,form - [('project', '=', active_id)] + [('projectid', '=', active_id)] { - 'default_project': active_id, + 'default_projectid': active_id 'show_project_update': True } @@ -40,29 +40,6 @@ - - DigitalSignage Vertrag - ir.actions.act_window - dss.contracts - form - - - - DigitalSignage Alle Vertraege - ir.actions.act_window - dss.contracts - kanban,tree,form - -

- No Contracts/Clients found. Let's create one! -

-

- Keep track of the progress of your contracts from creation to completion.
- Collaborate efficiently by chatting in real-time or via email. -

-
-
- dss.contracts.view.kanbanform dss.contracts @@ -107,11 +84,27 @@ {"default_allow_billable": 1} + + DigitalSignage Alle Vertraege + ir.actions.act_window + dss.contracts + tree,form,kanban + +

+ No Contracts/Clients found. Let's create one! +

+

+ Keep track of the progress of your contracts from creation to completion.
+ Collaborate efficiently by chatting in real-time or via email. +

+
+
dss_contracts_tree dss.contracts + @@ -123,7 +116,7 @@ - dss_contracts_form + dss_main_contracts_form dss.contracts @@ -179,7 +172,7 @@
- +
@@ -204,23 +197,23 @@
- +
- -
+ +
- +
- +
@@ -258,9 +251,9 @@ - - -
+
+ +
@@ -371,28 +364,61 @@
- --- - - - - - + + + + + + + + + - + - + + + + + +