/* This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. */ (function() { 'use strict'; var ENCODINGS = ["866", "ansi_x3.4-1968", "arabic", "ascii", "asmo-708", "big5", "big5-hkscs", "chinese", "cn-big5", "cp1250", "cp1251", "cp1252", "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp819", "cp866", "csbig5", "cseuckr", "cseucpkdfmtjapanese", "csgb2312", "csibm866", "csiso2022jp", "csiso2022kr", "csiso58gb231280", "csiso88596e", "csiso88596i", "csiso88598e", "csiso88598i", "csisolatin1", "csisolatin2", "csisolatin3", "csisolatin4", "csisolatin5", "csisolatin6", "csisolatin9", "csisolatinarabic", "csisolatincyrillic", "csisolatingreek", "csisolatinhebrew", "cskoi8r", "csksc56011987", "csmacintosh", "csshiftjis", "cyrillic", "dos-874", "ecma-114", "ecma-118", "elot_928", "euc-jp", "euc-kr", "gb18030", "gb2312", "gb_2312", "gb_2312-80", "gbk", "greek", "greek8", "hebrew", "hz-gb-2312", "ibm819", "ibm866", "iso-2022-cn", "iso-2022-cn-ext", "iso-2022-jp", "iso-2022-kr", "iso-8859-1", "iso-8859-10", "iso-8859-11", "iso-8859-13", "iso-8859-14", "iso-8859-15", "iso-8859-16", "iso-8859-2", "iso-8859-3", "iso-8859-4", "iso-8859-5", "iso-8859-6", "iso-8859-6-e", "iso-8859-6-i", "iso-8859-7", "iso-8859-8", "iso-8859-8-e", "iso-8859-8-i", "iso-8859-9", "iso-ir-100", "iso-ir-101", "iso-ir-109", "iso-ir-110", "iso-ir-126", "iso-ir-127", "iso-ir-138", "iso-ir-144", "iso-ir-148", "iso-ir-149", "iso-ir-157", "iso-ir-58", "iso8859-1", "iso8859-10", "iso8859-11", "iso8859-13", "iso8859-14", "iso8859-15", "iso8859-2", "iso8859-3", "iso8859-4", "iso8859-5", "iso8859-6", "iso8859-7", "iso8859-8", "iso8859-9", "iso88591", "iso885910", "iso885911", "iso885913", "iso885914", "iso885915", "iso88592", "iso88593", "iso88594", "iso88595", "iso88596", "iso88597", "iso88598", "iso88599", "iso_8859-1", "iso_8859-15", "iso_8859-1:1987", "iso_8859-2", "iso_8859-2:1987", "iso_8859-3", "iso_8859-3:1988", "iso_8859-4", "iso_8859-4:1988", "iso_8859-5", "iso_8859-5:1988", "iso_8859-6", "iso_8859-6:1987", "iso_8859-7", "iso_8859-7:1987", "iso_8859-8", "iso_8859-8:1988", "iso_8859-9", "iso_8859-9:1989", "koi", "koi8", "koi8-r", "koi8-ru", "koi8-u", "koi8_r", "korean", "ks_c_5601-1987", "ks_c_5601-1989", "ksc5601", "ksc_5601", "l1", "l2", "l3", "l4", "l5", "l6", "l9", "latin1", "latin2", "latin3", "latin4", "latin5", "latin6", "logical", "mac", "macintosh", "ms932", "ms_kanji", "shift-jis", "shift_jis", "sjis", "sun_eu_greek", "tis-620", "unicode-1-1-utf-8", "us-ascii", "utf-16", "utf-16be", "utf-16le", "utf-8", "utf8", "visual", "windows-1250", "windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", "windows-1256", "windows-1257", "windows-1258", "windows-31j", "windows-874", "windows-949", "x-cp1250", "x-cp1251", "x-cp1252", "x-cp1253", "x-cp1254", "x-cp1255", "x-cp1256", "x-cp1257", "x-cp1258", "x-euc-jp", "x-gbk", "x-mac-cyrillic", "x-mac-roman", "x-mac-ukrainian", "x-sjis", "x-user-defined", "x-x-big5"]; Sao.Window = {}; Sao.Window.InfoBar = Sao.class_(Object, { init: function() { this.el = jQuery('
', { 'class': 'infobar', }); this.__messages = new Set(); }, add: function(message, type, kind) { kind = kind || null; if (!message) { return; } var key = JSON.stringify([message, type]); if (!this.__messages.has(key)) { var infobar = jQuery('', { 'class': 'alert alert-dismissible alert-' + ( type || 'error'), 'role': 'alert', }).append(jQuery('', { 'type': 'button', 'class': 'close', 'aria-label': Sao.i18n.gettext("Close"), 'title': Sao.i18n.gettext("Close"), 'data-dismiss': 'alert', }).append(jQuery('', { 'aria-hidden': true, }).append('×')) ).append(jQuery('') .css('white-space','pre-wrap') .text(message)) .on('close.bs.alert', null, key, this.__response.bind(this)); this.el.append(infobar); infobar.data('kind', kind); } }, __response: function(evt) { this.__messages.add(evt.data); }, refresh: function(kind) { kind = kind || null; this.el.children().each((i, el) => { el = jQuery(el); if (el.data('kind') === kind) { el.remove(); } }); }, clear: function() { let kinds = new Set(); this.el.children().each( (i, el) => kinds.add(jQuery(el).data('kind'))); kinds.forEach(kind => { this.refresh(kind); }); this.__messages.clear(); }, }); Sao.Window.Form = Sao.class_(Object, { init: function(screen, callback, kwargs) { kwargs = kwargs || {}; this._position = undefined; this._length = 0; this.screen = screen; this.callback = callback; this.many = kwargs.many || 0; this.domain = kwargs.domain || null; this.context = kwargs.context || null; this.save_current = kwargs.save_current; var title_prm = jQuery.when(kwargs.title || '').then( title => { if (screen.breadcrumb.length) { var breadcrumb = jQuery.extend([], screen.breadcrumb); if (title) { breadcrumb.push(title); } this.title = breadcrumb.slice(-3, -1).map(function(x) { return Sao.common.ellipsize(x, 30); }).concat(breadcrumb.slice(-1)).join(' › '); if (breadcrumb.length > 3) { this.title = '... › ' + this.title; } } else { if (!title) { title = Sao.common.MODELNAME.get(this.screen.model_name); } this.title = title; } var revision = this.screen.context._datetime; var label; if (revision && Sao.common.MODELHISTORY.contains(this.screen.model_name)) { var date_format = Sao.common.date_format( this.screen.context.date_format); var time_format = '%H:%M:%S.%f'; var revision_label = ' @ ' + Sao.common.format_datetime( date_format + ' ' + time_format, revision); label = Sao.common.ellipsize( this.title, 80 - revision_label.length) + revision_label; title = this.title + revision_label; } else { label = Sao.common.ellipsize(this.title, 80); } return label; }); this.prev_view = screen.current_view; this.screen.screen_container.alternate_view = true; this.info_bar = new Sao.Window.InfoBar(); var view_type = kwargs.view_type || 'form'; this.switch_prm = this.screen.switch_view(view_type) .done(() => { if (kwargs.new_ && (this.screen.current_view.view_type == view_type)) { this.screen.new_(undefined, kwargs.defaults); } }); var dialog = new Sao.Dialog('', 'window-form', 'lg', false); this.dialog = dialog; this.el = dialog.modal; this.el.on('keydown', e => { if (e.which == Sao.common.ESC_KEYCODE) { e.preventDefault(); this.response('RESPONSE_CANCEL'); } }); var readonly = this.screen.group.readonly; this.but_ok = null; this.but_new = null; this._initial_value = null; this.view_type = view_type; if ((view_type == 'form') && !readonly) { let label; if (kwargs.new_) { label = Sao.i18n.gettext('Delete'); } else { label = Sao.i18n.gettext("Discard changes"); var record = this.screen.current_record; this._initial_value = record.get_on_change_value(); if (record.group.parent && record.model.fields[record.group.parent_name]) { var parent_field = record.model.fields[ record.group.parent_name]; this._initial_value[record.group.parent_name] = ( parent_field.get_eval(record)); } } dialog.footer.append(jQuery('', { 'class': 'btn btn-link', 'type': 'button', 'title': label, }).text(label).click(() => { this.response('RESPONSE_CANCEL'); })); } if (kwargs.new_ && this.many) { let label; if (this.save_current && !readonly) { label = Sao.i18n.gettext("Save and New"); } else { label = Sao.i18n.gettext("Add and New"); } dialog.footer.append(jQuery('', { 'class': 'btn btn-default', 'type': 'button', 'title': label, }).text(label).click(() => { this.response('RESPONSE_ACCEPT'); })); } if (this.save_current && !readonly) { let label = Sao.i18n.gettext("Save"); this.but_ok = jQuery('', { 'class': 'btn btn-primary', 'type': 'submit', 'title': label, }).text(label).appendTo(dialog.footer); } else { let label; if (readonly || (view_type == 'tree')) { label = Sao.i18n.gettext("Close"); } else if (kwargs.new_) { label = Sao.i18n.gettext("Add"); } else { label = Sao.i18n.gettext("Apply changes"); } this.but_ok = jQuery('', { 'class': 'btn btn-primary', 'type': 'submit', 'title': label, }).text(label).appendTo(dialog.footer); } dialog.content.submit(e => { e.preventDefault(); this.response('RESPONSE_OK'); }); if (view_type == 'tree') { var menu = jQuery('', { 'class': 'window-form-toolbar' }).appendTo(dialog.body); var group = jQuery('', { 'class': 'input-group input-group-sm' }).appendTo(menu); this.wid_text = jQuery('', { type: 'input' }).appendTo(menu); this.wid_text.hide(); var buttons = jQuery('', { 'class': 'input-group-btn' }).appendTo(group); var access = Sao.common.MODELACCESS.get(this.screen.model_name); var disable_during = function(callback) { return function(evt) { var button = jQuery(evt.target); button.prop('disabled', true); (callback(evt) || jQuery.when()) .always(function() { button.prop('disabled', false); }); }; }; this.but_switch = jQuery('', { 'class': 'btn btn-default btn-sm', 'type': 'button', 'aria-label': Sao.i18n.gettext("Switch"), 'title': Sao.i18n.gettext("Switch"), }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-switch') ).appendTo(buttons); this.but_switch.click( disable_during(this.switch_.bind(this))); this.but_previous = jQuery('', { 'class': 'btn btn-default btn-sm', 'type': 'button', 'aria-label': Sao.i18n.gettext("Previous"), 'title': Sao.i18n.gettext("Previous"), }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-back') ).appendTo(buttons); this.but_previous.click( disable_during(this.previous.bind(this))); this.label = jQuery('', { 'class': 'badge' }).appendTo(jQuery('', { 'class': 'btn hidden-xs', }).appendTo(buttons)); this.but_next = jQuery('', { 'class': 'btn btn-default btn-sm', 'type': 'button', 'aria-label': Sao.i18n.gettext("Next"), 'title': Sao.i18n.gettext("Next"), }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-forward') ).appendTo(buttons); this.but_next.click(disable_during(this.next.bind(this))); if (this.domain) { this.wid_text.show(); this.but_add = jQuery('', { 'class': 'btn btn-default btn-sm', 'type': 'button', 'aria-label': Sao.i18n.gettext("Add"), 'title': Sao.i18n.gettext("Add"), }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-add') ).appendTo(buttons); this.but_add.click(disable_during(this.add.bind(this))); this.but_add.prop('disabled', !access.read || readonly); this.but_remove = jQuery('', { 'class': 'btn btn-default btn-sm', 'type': 'button', 'aria-label': Sao.i18n.gettext("Remove"), 'title': Sao.i18n.gettext("Remove"), }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-remove') ).appendTo(buttons); this.but_remove.click( disable_during(this.remove.bind(this))); this.but_remove.prop('disabled', !access.read || readonly); } this.but_new = jQuery('', { 'class': 'btn btn-default btn-sm', 'type': 'button', 'aria-label': Sao.i18n.gettext("New"), 'title': Sao.i18n.gettext("New"), }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-create') ).appendTo(buttons); this.but_new.click(disable_during(this.new_.bind(this))); this.but_new.prop('disabled', !access.create || readonly); this.but_del = jQuery('', { 'class': 'btn btn-default btn-sm', 'type': 'button', 'aria-label': Sao.i18n.gettext("Delete"), 'title': Sao.i18n.gettext("Delete"), }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-delete') ).appendTo(buttons); this.but_del.click(disable_during(this.delete_.bind(this))); this.but_del.prop('disabled', !access['delete'] || readonly); this.but_undel = jQuery('', { 'class': 'btn btn-default btn-sm', 'type': 'button', 'aria-label': Sao.i18n.gettext("Undelete"), 'title': Sao.i18n.gettext("Undelete"), }).append(Sao.common.ICONFACTORY.get_icon_img('tryton-undo') ).appendTo(buttons); this.but_undel.click(disable_during(this.undelete.bind(this))); this.but_undel.prop('disabled', !access['delete'] || readonly); } var content = jQuery('').appendTo(dialog.body); dialog.body.append(this.info_bar.el); this.screen.windows.push(this); this.switch_prm.done(() => { if (this.screen.current_view.view_type != view_type) { this.destroy(); } else { title_prm.done(dialog.add_title.bind(dialog)); content.append(this.screen.screen_container.alternate_viewport); this.el.modal('show'); } }); this.el.on('shown.bs.modal', event => { this.screen.display().done(() => { this.screen.set_cursor(); }); }); this.el.on('hidden.bs.modal', function(event) { jQuery(this).remove(); }); }, record_message: function(position, size) { this._position = position; this._length = size; let name = '_'; if (position) { let selected = this.screen.selected_records.length; name = ' ' + position; if (selected > 1) { name += '#' + selected; } } let message = name + '/' + Sao.common.humanize(size); if (this.label) { this.label.text(message).attr('title', message); } this._set_button_sensitive(); }, record_modified: function() { this.info_bar.refresh(); this._set_button_sensitive(); }, _set_button_sensitive: function() { if (this.view_type != 'tree') { return; } let access = Sao.common.MODELACCESS.get(this.screen.model_name); let first = false, last = false; if (typeof this._position == 'number') { first = this._position <= 1; last = this._position >= this._length; } var deletable = this.screen.deletable && this.screen.selected_records.some((r) => !r.deleted && !r.removed); let undeletable = this.screen.selected_records.some((r) => r.deleted || r.removed); let view_type = this.screen.current_view.view_type; let has_views = this.screen.number_of_views > 1; let readonly = this.screen.group.readonly; this.but_switch.prop( 'disabled', !((this._position || (view_type == 'form')) && has_views)); this.but_new.prop( 'disabled', readonly || !access.create); this.but_del.prop( 'disabled', readonly || !access.delete || !deletable); this.but_undel.prop( 'disabled', readonly || !undeletable || (typeof this._position != 'number')); this.but_next.prop( 'disabled', !this._length || last); this.but_previous.prop( 'disabled', !this._length || first); if (this.domain) { this.but_add.prop( 'disabled', readonly || !access.write_access || !access.readonly); this.wid_text.prop('disabled', this.but_add.prop('disabled')); this.but_remove.prop( 'disabled', readonly || (typeof this._position != 'number') || !access.write || !access.read); } }, add: function() { var domain = jQuery.extend([], this.domain); var model_name = this.screen.model_name; var value = this.wid_text.val(); const callback = result => { var prm = jQuery.when(); if (!jQuery.isEmptyObject(result)) { var ids = []; for (const record of result) { ids.push(record[0]); } this.screen.group.load(ids, true, -1, null); prm = this.screen.display(); } prm.done(() => { this.screen.set_cursor(); }); this.entry.val(''); }; var parser = new Sao.common.DomainParser(); new Sao.Window.Search(model_name, callback, { sel_multi: true, context: this.context, domain: domain, search_filter: parser.quote(value) }); }, remove: function() { this.screen.remove(false, true, false); }, new_: function() { this.screen.new_(); this._initial_value = null; this.many -= 1; if (this.many == 0) { this.but_new.addClass('disabled'); } }, delete_: function() { this.screen.remove(false, false, false); }, undelete: function() { this.screen.unremove(); }, previous: function() { return this.screen.display_previous(); }, next: function() { return this.screen.display_next(); }, switch_: function() { return this.screen.switch_view(); }, response: function(response_id) { var result; this.screen.current_view.set_value(); var readonly = this.screen.group.readonly; if (~['RESPONSE_OK', 'RESPONSE_ACCEPT'].indexOf(response_id) && !readonly && this.screen.current_record) { jQuery.when(this.screen.current_record.validate()).then(validate => { if (validate && this.screen.attributes.pre_validate) { return this.screen.current_record.pre_validate().then( () => true, () => false); } return validate; }).then(validate => { var closing_prm = jQuery.Deferred(); if (validate && this.save_current) { this.screen.save_current().then(closing_prm.resolve, closing_prm.reject); } else if (validate && this.screen.current_view.view_type == 'form') { var view = this.screen.current_view; var validate_prms = []; for (var name in view.widgets) { var widget = view.widgets[name]; if (widget.screen && widget.screen.attributes.pre_validate) { var record = widget.screen.current_record; if (record) { validate_prms.push(record.pre_validate()); } } } jQuery.when.apply(jQuery, validate_prms).then( closing_prm.resolve, closing_prm.reject); } else if (!validate) { this.info_bar.add( this.screen.invalid_message(), 'danger'); closing_prm.reject(); } else { this.info_bar.clear(); closing_prm.resolve(); } closing_prm.fail(() => { this.screen.display().done(() => { this.screen.set_cursor(); }); }); // TODO Add support for many closing_prm.done(() => { if (response_id == 'RESPONSE_ACCEPT') { this.screen.new_(); this.screen.current_view.display().done(() => { this.screen.set_cursor(); }); this.many -= 1; if (this.many === 0) { this.but_new.prop('disabled', true); } } else { result = true; if (this.callback) { this.callback(result); } this.destroy(); } }); }); return; } var cancel_prm = null; if (response_id == 'RESPONSE_CANCEL' && !readonly && this.screen.current_record) { result = false; var record = this.screen.current_record; var added = record.modified_fields.id; if ((record.id < 0) || this.save_current) { cancel_prm = this.screen.cancel_current( this._initial_value); } else if (record.modified) { record.cancel(); cancel_prm = record.reload().then(() => { this.screen.display(); }); } if (added) { record.modified_fields.id = added; } } else { result = (response_id != 'RESPONSE_CANCEL') && !readonly; } (cancel_prm || jQuery.when()).then(() => { if (this.callback) { this.callback(result); } this.destroy(); }); }, destroy: function() { this.screen.windows.splice(this.screen.windows.indexOf(this), 1); this.screen.screen_container.alternate_view = false; this.screen.screen_container.alternate_viewport.children() .detach(); if (this.prev_view) { // Empty when opening from Many2One this.screen.switch_view(this.prev_view.view_type); } this.el.modal('hide'); } }); Sao.Window.Attachment = Sao.class_(Sao.Window.Form, { init: function(record, callback) { this.resource = record.model.name + ',' + record.id; this.attachment_callback = callback; var context = jQuery.extend({}, record.get_context()); var screen = new Sao.Screen('ir.attachment', { domain: [['resource', '=', this.resource]], mode: ['tree', 'form'], context: context, }); var title = record.rec_name().then(function(rec_name) { return Sao.i18n.gettext('Attachments (%1)', rec_name); }); Sao.Window.Attachment._super.init.call(this, screen, this.callback, {view_type: 'tree', title: title}); this.switch_prm = this.switch_prm.then(function() { return screen.search_filter(); }); }, callback: function(result) { var prm = jQuery.when(); if (result) { prm = this.screen.save_current(); } if (this.attachment_callback) { prm.always(this.attachment_callback.bind(this)); } }, add_data: function(data, filename) { var screen = this.screen; this.switch_prm.then(function() { screen.new_().then(function(record) { var data_field = record.model.fields.data; record.field_set_client( data_field.description.filename, filename); record.field_set_client('data', data); screen.display(); }); }); }, add_uri: function(uri) { var screen = this.screen; this.switch_prm.then(function() { screen.current_record = null; screen.switch_view('form').then(function() { screen.new_().then(function(record) { record.field_set_client('link', uri); record.field_set_client('type', 'link'); screen.display(); }); }); }); }, add_text: function(text) { var screen = this.screen; this.switch_prm.then(function() { screen.current_record = null; screen.switch_view('form').then(function() { screen.new_().then(function(record) { record.field_set_client('description', text); screen.display(); }); }); }); }, }); Sao.Window.Attachment.get_attachments = function(record) { var prm; if (record && (record.id >= 0)) { var context = record.get_context(); prm = Sao.rpc({ 'method': 'model.ir.attachment.search_read', 'params': [ [['resource', '=', record.model.name + ',' + record.id]], 0, 20, null, ['rec_name', 'name', 'type', 'link'], context], }, record.model.session); } else { prm = jQuery.when([]); } var partial = function(callback, attachment, context, session) { return function() { return callback(attachment, context, session); }; }; return prm.then(function(attachments) { return attachments.map(function(attachment) { var name = attachment.rec_name; if (attachment.type == 'link') { return [name, attachment.link]; } else { var callback = Sao.Window.Attachment[ 'open_' + attachment.type]; return [name, partial( callback, attachment, context, record.model.session)]; } }); }); }; Sao.Window.Attachment.open_data = function(attachment, context, session) { Sao.rpc({ 'method': 'model.ir.attachment.read', 'params': [ [attachment.id], ['data'], context], }, session).then(function(values) { Sao.common.download_file(values[0].data, attachment.name); }); }; Sao.Window.Note = Sao.class_(Sao.Window.Form, { init: function(record, callback) { this.resource = record.model.name + ',' + record.id; this.note_callback = callback; var context = jQuery.extend({}, record.get_context()); var screen = new Sao.Screen('ir.note', { domain: [['resource', '=', this.resource]], mode: ['tree', 'form'], context: context, }); var title = record.rec_name().then(function(rec_name) { return Sao.i18n.gettext('Notes (%1)', rec_name); }); Sao.Window.Note._super.init.call(this, screen, this.callback, {view_type: 'tree', title: title}); this.switch_prm = this.switch_prm.then(function() { return screen.search_filter(); }); }, callback: function(result) { var prm = jQuery.when(); if (result) { var unread = this.screen.group.model.fields.unread; for (const record of this.screen.group) { if (record.get_loaded() || record.id < 0) { if (!record.modified_fields.unread) { unread.set_client(record, false); } } } prm = this.screen.save_current(); } if (this.note_callback) { prm.always(this.note_callback.bind(this)); } } }); Sao.Window.Log = Sao.class_(Sao.Window.Form, { init: function(record) { this.resource = record.model.name + ',' + record.id; var title = record.rec_name().then(function(rec_name) { return Sao.i18n.gettext("Logs (%1)", rec_name); }); var context = jQuery.extend({}, record.get_context()); var form = jQuery('', { 'class': 'form', }); var table = jQuery('