diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/mrp/static | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/mrp/static')
39 files changed, 1365 insertions, 0 deletions
diff --git a/addons/mrp/static/description/icon.png b/addons/mrp/static/description/icon.png Binary files differnew file mode 100644 index 00000000..2fa36974 --- /dev/null +++ b/addons/mrp/static/description/icon.png diff --git a/addons/mrp/static/description/icon.svg b/addons/mrp/static/description/icon.svg new file mode 100644 index 00000000..cca7bcfd --- /dev/null +++ b/addons/mrp/static/description/icon.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70"><defs><path id="a" d="M4 0h61c4 0 5 1 5 5v60c0 4-1 5-5 5H4c-3 0-4-1-4-5V5c0-4 1-5 4-5z"/><linearGradient id="c" x1="100%" x2="0%" y1="0%" y2="100%"><stop offset="0%" stop-color="#7CC098"/><stop offset="100%" stop-color="#5F8A71"/></linearGradient><path id="d" d="M53.373 32.454c.742 0 1.206.784.824 1.4-2.009 3.235-5.668 5.4-9.848 5.4-6.318 0-11.445-4.944-11.484-11.056C32.825 22.058 38.012 17 44.349 17c4.176 0 7.831 2.16 9.842 5.389.385.62-.07 1.41-.818 1.41h-8.386l-3.19 4.328 3.19 4.327h8.386zM39.684 39.64l-15.97 15.473c-1.994 1.931-5.226 1.931-7.22 0a4.837 4.837 0 0 1 0-6.994l15.971-15.472c1.289 3.183 3.932 5.744 7.219 6.993zm-16.39 10.74c0-1.024-.857-1.854-1.914-1.854s-1.914.83-1.914 1.854.857 1.854 1.914 1.854 1.914-.83 1.914-1.854z"/><path id="e" d="M53.373 30.454c.742 0 1.206.784.824 1.4-2.009 3.235-5.668 5.4-9.848 5.4-6.318 0-11.445-4.944-11.484-11.056C32.825 20.058 38.012 15 44.349 15c4.176 0 7.831 2.16 9.842 5.389.385.62-.07 1.41-.818 1.41h-8.386l-3.19 4.328 3.19 4.327h8.386zM39.684 37.64l-15.97 15.473c-1.994 1.931-5.226 1.931-7.22 0a4.837 4.837 0 0 1 0-6.994l15.971-15.472c1.289 3.183 3.932 5.744 7.219 6.993zm-16.39 10.74c0-1.024-.857-1.854-1.914-1.854s-1.914.83-1.914 1.854.857 1.854 1.914 1.854 1.914-.83 1.914-1.854z"/></defs><g fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><g mask="url(#b)"><path fill="url(#c)" d="M0 0H70V70H0z"/><path fill="#FFF" fill-opacity=".383" d="M4 1h61c2.667 0 4.333.667 5 2V0H0v3c.667-1.333 2-2 4-2z"/><path fill="#393939" d="M4 69c-2 0-4-1-4-4V36.075l15.053-15.2 18.452.218 2.734 6.93v4.524L53.62 51.98 42.667 69H4z" opacity=".324"/><path fill="#000" fill-opacity=".383" d="M4 69h61c2.667 0 4.333-1 5-3v4H0v-4c.667 2 2 3 4 3z"/><use fill="#000" fill-rule="nonzero" opacity=".3" transform="matrix(-1 0 0 1 69.334 0)" xlink:href="#d"/><use fill="#FFF" fill-rule="nonzero" transform="matrix(-1 0 0 1 69.334 0)" xlink:href="#e"/></g></g></svg>
\ No newline at end of file diff --git a/addons/mrp/static/img/assebly-worksheet.pdf b/addons/mrp/static/img/assebly-worksheet.pdf Binary files differnew file mode 100644 index 00000000..c5975da4 --- /dev/null +++ b/addons/mrp/static/img/assebly-worksheet.pdf diff --git a/addons/mrp/static/img/cutting-worksheet.pdf b/addons/mrp/static/img/cutting-worksheet.pdf Binary files differnew file mode 100644 index 00000000..10b86755 --- /dev/null +++ b/addons/mrp/static/img/cutting-worksheet.pdf diff --git a/addons/mrp/static/img/drill-worksheet.pdf b/addons/mrp/static/img/drill-worksheet.pdf Binary files differnew file mode 100644 index 00000000..86d58a6c --- /dev/null +++ b/addons/mrp/static/img/drill-worksheet.pdf diff --git a/addons/mrp/static/img/product_product_computer_desk_bolt.png b/addons/mrp/static/img/product_product_computer_desk_bolt.png Binary files differnew file mode 100644 index 00000000..4bfe2ee8 --- /dev/null +++ b/addons/mrp/static/img/product_product_computer_desk_bolt.png diff --git a/addons/mrp/static/img/product_product_computer_desk_screw.png b/addons/mrp/static/img/product_product_computer_desk_screw.png Binary files differnew file mode 100644 index 00000000..897182ba --- /dev/null +++ b/addons/mrp/static/img/product_product_computer_desk_screw.png diff --git a/addons/mrp/static/img/product_product_drawer_black.png b/addons/mrp/static/img/product_product_drawer_black.png Binary files differnew file mode 100644 index 00000000..59a1178c --- /dev/null +++ b/addons/mrp/static/img/product_product_drawer_black.png diff --git a/addons/mrp/static/img/product_product_drawer_case_black.png b/addons/mrp/static/img/product_product_drawer_case_black.png Binary files differnew file mode 100644 index 00000000..5534f67f --- /dev/null +++ b/addons/mrp/static/img/product_product_drawer_case_black.png diff --git a/addons/mrp/static/img/product_product_plastic_laminate.png b/addons/mrp/static/img/product_product_plastic_laminate.png Binary files differnew file mode 100644 index 00000000..a7b2083c --- /dev/null +++ b/addons/mrp/static/img/product_product_plastic_laminate.png diff --git a/addons/mrp/static/img/product_product_ply_veneer.png b/addons/mrp/static/img/product_product_ply_veneer.png Binary files differnew file mode 100644 index 00000000..cf2c834c --- /dev/null +++ b/addons/mrp/static/img/product_product_ply_veneer.png diff --git a/addons/mrp/static/img/product_product_table_kit.png b/addons/mrp/static/img/product_product_table_kit.png Binary files differnew file mode 100644 index 00000000..73d47f10 --- /dev/null +++ b/addons/mrp/static/img/product_product_table_kit.png diff --git a/addons/mrp/static/img/product_product_wood_panel.png b/addons/mrp/static/img/product_product_wood_panel.png Binary files differnew file mode 100644 index 00000000..9b609954 --- /dev/null +++ b/addons/mrp/static/img/product_product_wood_panel.png diff --git a/addons/mrp/static/img/product_product_wood_ply.png b/addons/mrp/static/img/product_product_wood_ply.png Binary files differnew file mode 100644 index 00000000..fc4ed564 --- /dev/null +++ b/addons/mrp/static/img/product_product_wood_ply.png diff --git a/addons/mrp/static/img/product_product_wood_wear.png b/addons/mrp/static/img/product_product_wood_wear.png Binary files differnew file mode 100644 index 00000000..2b6dc394 --- /dev/null +++ b/addons/mrp/static/img/product_product_wood_wear.png diff --git a/addons/mrp/static/img/table.png b/addons/mrp/static/img/table.png Binary files differnew file mode 100644 index 00000000..770e74e2 --- /dev/null +++ b/addons/mrp/static/img/table.png diff --git a/addons/mrp/static/img/table_leg.png b/addons/mrp/static/img/table_leg.png Binary files differnew file mode 100644 index 00000000..22a99e4d --- /dev/null +++ b/addons/mrp/static/img/table_leg.png diff --git a/addons/mrp/static/img/table_top.png b/addons/mrp/static/img/table_top.png Binary files differnew file mode 100644 index 00000000..e17aed23 --- /dev/null +++ b/addons/mrp/static/img/table_top.png diff --git a/addons/mrp/static/src/img/mrp-tablet.png b/addons/mrp/static/src/img/mrp-tablet.png Binary files differnew file mode 100644 index 00000000..f60fbbb7 --- /dev/null +++ b/addons/mrp/static/src/img/mrp-tablet.png diff --git a/addons/mrp/static/src/js/mrp.js b/addons/mrp/static/src/js/mrp.js new file mode 100644 index 00000000..f895aef9 --- /dev/null +++ b/addons/mrp/static/src/js/mrp.js @@ -0,0 +1,268 @@ +odoo.define('mrp.mrp_state', function (require) { +"use strict"; + +var AbstractField = require('web.AbstractField'); +var core = require('web.core'); +var fields = require('web.basic_fields'); +var fieldUtils = require('web.field_utils'); +var field_registry = require('web.field_registry'); +var time = require('web.time'); + +var _t = core._t; + +/** + * This widget is used to display the availability on a workorder. + */ +var SetBulletStatus = AbstractField.extend({ + // as this widget is based on hardcoded values, use it in another context + // probably won't work + // supportedFieldTypes: ['selection'], + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + this.classes = this.nodeOptions && this.nodeOptions.classes || {}; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @override + */ + _renderReadonly: function () { + this._super.apply(this, arguments); + var bullet_class = this.classes[this.value] || 'default'; + if (this.value) { + var title = this.value === 'waiting' ? _t('Waiting Materials') : ''; + this.$el.attr({'title': title, 'style': 'display:inline'}); + this.$el.removeClass('text-success text-danger text-default'); + this.$el.html($('<span>' + title + '</span>').addClass('badge badge-' + bullet_class)); + } + } +}); + +var TimeCounter = fields.FieldFloatTime.extend({ + + init: function () { + this._super.apply(this, arguments); + this.duration = this.record.data.duration; + }, + + willStart: function () { + var self = this; + var def = this._rpc({ + model: 'mrp.workcenter.productivity', + method: 'search_read', + domain: [ + ['workorder_id', '=', this.record.data.id], + ['date_end', '=', false], + ], + }).then(function (result) { + var currentDate = new Date(); + var duration = 0; + if (result.length > 0) { + duration += self._getDateDifference(time.auto_str_to_date(result[0].date_start), currentDate); + } + var minutes = duration / 60 >> 0; + var seconds = duration % 60; + self.duration += minutes + seconds / 60; + if (self.mode === 'edit') { + self.value = self.duration; + } + }); + return Promise.all([this._super.apply(this, arguments), def]); + }, + + destroy: function () { + this._super.apply(this, arguments); + clearTimeout(this.timer); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + isSet: function () { + return true; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Compute the difference between two dates. + * + * @private + * @param {string} dateStart + * @param {string} dateEnd + * @returns {integer} the difference in millisecond + */ + _getDateDifference: function (dateStart, dateEnd) { + return moment(dateEnd).diff(moment(dateStart), 'seconds'); + }, + /** + * @override + */ + _renderReadonly: function () { + if (this.record.data.is_user_working) { + this._startTimeCounter(); + } else { + this._super.apply(this, arguments); + } + }, + /** + * @private + */ + _startTimeCounter: function () { + var self = this; + clearTimeout(this.timer); + if (this.record.data.is_user_working) { + this.timer = setTimeout(function () { + self.duration += 1/60; + self._startTimeCounter(); + }, 1000); + } else { + clearTimeout(this.timer); + } + this.$el.text(fieldUtils.format.float_time(this.duration)); + }, +}); + +var FieldEmbedURLViewer = fields.FieldChar.extend({ + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + this.page = 1; + this.srcDirty = false; + }, + + /** + * force to set 'src' for embed iframe viewer when its value has changed + * + * @override + * + */ + reset: function () { + this._super.apply(this, arguments); + this._updateIframePreview(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Initializes and returns an iframe for the viewer + * + * @private + * @returns {jQueryElement} + */ + _prepareIframe: function () { + return $('<iframe>', { + class: 'o_embed_iframe d-none', + allowfullscreen: true, + }); + }, + + /** + * @override + * @private + */ + _renderEdit: function () { + if (!this.$('iframe.o_embed_iframe').length) { + this.$input = this.$el; + this.setElement(this.$el.wrap('<div class="o_embed_url_viewer o_field_widget"/>').parent()); + this.$el.append(this._prepareIframe()); + } + this._prepareInput(this.$input); + + // Do not set iframe src if widget is invisible + if (!this.record.evalModifiers(this.attrs.modifiers).invisible) { + this._updateIframePreview(); + } else { + this.srcDirty = true; + } + }, + /** + * @override + * @private + */ + _renderReadonly: function () { + if (!this.$('iframe.o_embed_iframe').length) { + this.$el.addClass('o_embed_url_viewer'); + this.$el.append(this._prepareIframe()); + } + this._updateIframePreview(); + }, + /** + * Set the associated src for embed iframe viewer + * + * @private + * @returns {string} source of the google slide + */ + _getEmbedSrc: function () { + var src = false; + if (this.value) { + // check given google slide url is valid or not + var googleRegExp = /(^https:\/\/docs.google.com).*(\/d\/e\/|\/d\/)([A-Za-z0-9-_]+)/; + var google = this.value.match(googleRegExp); + if (google && google[3]) { + src = 'https://docs.google.com/presentation' + google[2] + google[3] + '/preview?slide=' + this.page; + } + } + return src || this.value; + }, + /** + * update iframe attrs + * + * @private + */ + _updateIframePreview: function () { + var $iframe = this.$('iframe.o_embed_iframe'); + var src = this._getEmbedSrc(); + $iframe.toggleClass('d-none', !src); + if (src) { + $iframe.attr('src', src); + } else { + $iframe.removeAttr('src'); + } + }, + /** + * Listen to modifiers updates to and only render iframe when it is necessary + * + * @override + */ + updateModifiersValue: function () { + this._super.apply(this, arguments); + if (!this.attrs.modifiersValue.invisible && this.srcDirty) { + this._updateIframePreview(); + this.srcDirty = false; + } + }, +}); + + +field_registry + .add('bullet_state', SetBulletStatus) + .add('mrp_time_counter', TimeCounter) + .add('embed_viewer', FieldEmbedURLViewer); + +fieldUtils.format.mrp_time_counter = fieldUtils.format.float_time; + +return FieldEmbedURLViewer; +}); diff --git a/addons/mrp/static/src/js/mrp_bom_report.js b/addons/mrp/static/src/js/mrp_bom_report.js new file mode 100644 index 00000000..cb56c797 --- /dev/null +++ b/addons/mrp/static/src/js/mrp_bom_report.js @@ -0,0 +1,228 @@ +odoo.define('mrp.mrp_bom_report', function (require) { +'use strict'; + +var core = require('web.core'); +var framework = require('web.framework'); +var stock_report_generic = require('stock.stock_report_generic'); + +var QWeb = core.qweb; +var _t = core._t; + +var MrpBomReport = stock_report_generic.extend({ + events: { + 'click .o_mrp_bom_unfoldable': '_onClickUnfold', + 'click .o_mrp_bom_foldable': '_onClickFold', + 'click .o_mrp_bom_action': '_onClickAction', + 'click .o_mrp_show_attachment_action': '_onClickShowAttachment', + }, + get_html: function() { + var self = this; + var args = [ + this.given_context.active_id, + this.given_context.searchQty || false, + this.given_context.searchVariant, + ]; + return this._rpc({ + model: 'report.mrp.report_bom_structure', + method: 'get_html', + args: args, + context: this.given_context, + }) + .then(function (result) { + self.data = result; + if (! self.given_context.searchVariant) { + self.given_context.searchVariant = result.is_variant_applied && Object.keys(result.variants)[0]; + } + }); + }, + set_html: function() { + var self = this; + return this._super().then(function () { + self.$('.o_content').html(self.data.lines); + self.renderSearch(); + self.update_cp(); + }); + }, + render_html: function(event, $el, result){ + if (result.indexOf('mrp.document') > 0) { + if (this.$('.o_mrp_has_attachments').length === 0) { + var column = $('<th/>', { + class: 'o_mrp_has_attachments', + title: 'Files attached to the product Attachments', + text: 'Attachments', + }); + this.$('table thead th:last-child').after(column); + } + } + $el.after(result); + $(event.currentTarget).toggleClass('o_mrp_bom_foldable o_mrp_bom_unfoldable fa-caret-right fa-caret-down'); + this._reload_report_type(); + }, + get_bom: function(event) { + var self = this; + var $parent = $(event.currentTarget).closest('tr'); + var activeID = $parent.data('id'); + var productID = $parent.data('product_id'); + var lineID = $parent.data('line'); + var qty = $parent.data('qty'); + var level = $parent.data('level') || 0; + return this._rpc({ + model: 'report.mrp.report_bom_structure', + method: 'get_bom', + args: [ + activeID, + productID, + parseFloat(qty), + lineID, + level + 1, + ] + }) + .then(function (result) { + self.render_html(event, $parent, result); + }); + }, + get_operations: function(event) { + var self = this; + var $parent = $(event.currentTarget).closest('tr'); + var activeID = $parent.data('bom-id'); + var qty = $parent.data('qty'); + var level = $parent.data('level') || 0; + return this._rpc({ + model: 'report.mrp.report_bom_structure', + method: 'get_operations', + args: [ + activeID, + parseFloat(qty), + level + 1 + ] + }) + .then(function (result) { + self.render_html(event, $parent, result); + }); + }, + update_cp: function () { + var status = { + cp_content: { + $buttons: this.$buttonPrint, + $searchview: this.$searchView + }, + }; + return this.updateControlPanel(status); + }, + renderSearch: function () { + this.$buttonPrint = $(QWeb.render('mrp.button', {'is_variant_applied': this.data.is_variant_applied})); + this.$buttonPrint.find('.o_mrp_bom_print').on('click', this._onClickPrint.bind(this)); + this.$buttonPrint.find('.o_mrp_bom_print_all_variants').on('click', this._onClickPrint.bind(this)); + this.$buttonPrint.find('.o_mrp_bom_print_unfolded').on('click', this._onClickPrint.bind(this)); + this.$searchView = $(QWeb.render('mrp.report_bom_search', _.omit(this.data, 'lines'))); + this.$searchView.find('.o_mrp_bom_report_qty').on('change', this._onChangeQty.bind(this)); + this.$searchView.find('.o_mrp_bom_report_variants').on('change', this._onChangeVariants.bind(this)); + this.$searchView.find('.o_mrp_bom_report_type').on('change', this._onChangeType.bind(this)); + }, + _onClickPrint: function (ev) { + var childBomIDs = _.map(this.$el.find('.o_mrp_bom_foldable').closest('tr'), function (el) { + return $(el).data('id'); + }); + framework.blockUI(); + var reportname = 'mrp.report_bom_structure?docids=' + this.given_context.active_id + + '&report_type=' + this.given_context.report_type + + '&quantity=' + (this.given_context.searchQty || 1); + if (! $(ev.currentTarget).hasClass('o_mrp_bom_print_unfolded')) { + reportname += '&childs=' + JSON.stringify(childBomIDs); + } + if ($(ev.currentTarget).hasClass('o_mrp_bom_print_all_variants')) { + reportname += '&all_variants=' + 1; + } else if (this.given_context.searchVariant) { + reportname += '&variant=' + this.given_context.searchVariant; + } + var action = { + 'type': 'ir.actions.report', + 'report_type': 'qweb-pdf', + 'report_name': reportname, + 'report_file': 'mrp.report_bom_structure', + }; + return this.do_action(action).then(function (){ + framework.unblockUI(); + }); + }, + _onChangeQty: function (ev) { + var qty = $(ev.currentTarget).val().trim(); + if (qty) { + this.given_context.searchQty = parseFloat(qty); + this._reload(); + } + }, + _onChangeType: function (ev) { + var report_type = $("option:selected", $(ev.currentTarget)).data('type'); + this.given_context.report_type = report_type; + this._reload_report_type(); + }, + _onChangeVariants: function (ev) { + this.given_context.searchVariant = $(ev.currentTarget).val(); + this._reload(); + }, + _onClickUnfold: function (ev) { + var redirect_function = $(ev.currentTarget).data('function'); + this[redirect_function](ev); + }, + _onClickFold: function (ev) { + this._removeLines($(ev.currentTarget).closest('tr')); + $(ev.currentTarget).toggleClass('o_mrp_bom_foldable o_mrp_bom_unfoldable fa-caret-right fa-caret-down'); + }, + _onClickAction: function (ev) { + ev.preventDefault(); + return this.do_action({ + type: 'ir.actions.act_window', + res_model: $(ev.currentTarget).data('model'), + res_id: $(ev.currentTarget).data('res-id'), + context: { + 'active_id': $(ev.currentTarget).data('res-id') + }, + views: [[false, 'form']], + target: 'current' + }); + }, + _onClickShowAttachment: function (ev) { + ev.preventDefault(); + var ids = $(ev.currentTarget).data('res-id'); + return this.do_action({ + name: _t('Attachments'), + type: 'ir.actions.act_window', + res_model: $(ev.currentTarget).data('model'), + domain: [['id', 'in', ids]], + views: [[false, 'kanban'], [false, 'list'], [false, 'form']], + view_mode: 'kanban,list,form', + target: 'current', + }); + }, + _reload: function () { + var self = this; + return this.get_html().then(function () { + self.$('.o_content').html(self.data.lines); + self._reload_report_type(); + }); + }, + _reload_report_type: function () { + this.$('.o_mrp_bom_cost.o_hidden, .o_mrp_prod_cost.o_hidden').toggleClass('o_hidden'); + if (this.given_context.report_type === 'bom_structure') { + this.$('.o_mrp_bom_cost, .o_mrp_prod_cost').toggleClass('o_hidden'); + } + }, + _removeLines: function ($el) { + var self = this; + var activeID = $el.data('id'); + _.each(this.$('tr[parent_id='+ activeID +']'), function (parent) { + var $parent = self.$(parent); + var $el = self.$('tr[parent_id='+ $parent.data('id') +']'); + if ($el.length) { + self._removeLines($parent); + } + $parent.remove(); + }); + }, +}); + +core.action_registry.add('mrp_bom_report', MrpBomReport); +return MrpBomReport; + +}); diff --git a/addons/mrp/static/src/js/mrp_document_kanban_view.js b/addons/mrp/static/src/js/mrp_document_kanban_view.js new file mode 100644 index 00000000..e4e93663 --- /dev/null +++ b/addons/mrp/static/src/js/mrp_document_kanban_view.js @@ -0,0 +1,20 @@ +odoo.define('mrp.MrpDocumentsKanbanView', function (require) { +"use strict"; + +const KanbanView = require('web.KanbanView'); +const MrpDocumentsKanbanController = require('mrp.MrpDocumentsKanbanController'); +const MrpDocumentsKanbanRenderer = require('mrp.MrpDocumentsKanbanRenderer'); +const viewRegistry = require('web.view_registry'); + +const MrpDocumentsKanbanView = KanbanView.extend({ + config: Object.assign({}, KanbanView.prototype.config, { + Controller: MrpDocumentsKanbanController, + Renderer: MrpDocumentsKanbanRenderer, + }), +}); + +viewRegistry.add('mrp_documents_kanban', MrpDocumentsKanbanView); + +return MrpDocumentsKanbanView; + +}); diff --git a/addons/mrp/static/src/js/mrp_documents_controller_mixin.js b/addons/mrp/static/src/js/mrp_documents_controller_mixin.js new file mode 100644 index 00000000..9bcaf3d6 --- /dev/null +++ b/addons/mrp/static/src/js/mrp_documents_controller_mixin.js @@ -0,0 +1,126 @@ +odoo.define('mrp.controllerMixin', function (require) { +'use strict'; + +const { _t, qweb } = require('web.core'); +const fileUploadMixin = require('web.fileUploadMixin'); +const DocumentViewer = require('mrp.MrpDocumentViewer'); + +const MrpDocumentsControllerMixin = Object.assign({}, fileUploadMixin, { + events: { + 'click .o_mrp_documents_kanban_upload': '_onClickMrpDocumentsUpload', + }, + custom_events: Object.assign({}, fileUploadMixin.custom_events, { + kanban_image_clicked: '_onKanbanPreview', + upload_file: '_onUploadFile', + }), + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Called right after the reload of the view. + */ + async reload() { + await this._renderFileUploads(); + }, + + /** + * @override + * @param {jQueryElement} $node + */ + renderButtons($node) { + this.$buttons = $(qweb.render('MrpDocumentsKanbanView.buttons')); + this.$buttons.appendTo($node); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + _getFileUploadRoute() { + return '/mrp/upload_attachment'; + }, + + /** + * @override + * @param {integer} param0.recordId + */ + _makeFileUploadFormDataKeys() { + const context = this.model.get(this.handle, { raw: true }).getContext(); + return { + res_id: context.default_res_id, + res_model: context.default_res_model, + }; + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onClickMrpDocumentsUpload() { + const $uploadInput = $('<input>', { + type: 'file', + name: 'files[]', + multiple: 'multiple' + }); + $uploadInput.on('change', async ev => { + await this._uploadFiles(ev.target.files); + $uploadInput.remove(); + }); + $uploadInput.click(); + }, + + /** + * Handles custom event to display the document viewer. + * + * @private + * @param {OdooEvent} ev + * @param {integer} ev.data.recordID + * @param {Array<Object>} ev.data.recordList + */ + _onKanbanPreview(ev) { + ev.stopPropagation(); + const documents = ev.data.recordList; + const documentID = ev.data.recordID; + const documentViewer = new DocumentViewer(this, documents, documentID); + documentViewer.appendTo(this.$('.o_mrp_documents_kanban_view')); + }, + + /** + * Specially created to call `_uploadFiles` method from tests. + * + * @private + * @param {OdooEvent} ev + */ + async _onUploadFile(ev) { + await this._uploadFiles(ev.data.files); + }, + + /** + * @override + * @param {Object} param0 + * @param {XMLHttpRequest} param0.xhr + */ + _onUploadLoad({ xhr }) { + const result = xhr.status === 200 + ? JSON.parse(xhr.response) + : { + error: _.str.sprintf(_t("status code: %s </br> message: %s"), xhr.status, xhr.response) + }; + if (result.error) { + this.do_notify(_t("Error"), result.error, true); + } + fileUploadMixin._onUploadLoad.apply(this, arguments); + }, +}); + +return MrpDocumentsControllerMixin; + +}); diff --git a/addons/mrp/static/src/js/mrp_documents_document_viewer.js b/addons/mrp/static/src/js/mrp_documents_document_viewer.js new file mode 100644 index 00000000..c0d7f3e8 --- /dev/null +++ b/addons/mrp/static/src/js/mrp_documents_document_viewer.js @@ -0,0 +1,19 @@ +odoo.define('mrp.MrpDocumentViewer', function (require) { +"use strict"; + +const DocumentViewer = require('mail.DocumentViewer'); + +/** + * This file defines the DocumentViewer for the MRP Documents Kanban view. + */ +const MrpDocumentsDocumentViewer = DocumentViewer.extend({ + init(parent, attachments, activeAttachmentID) { + this._super(...arguments); + this.modelName = 'mrp.document'; + }, +}); + +return MrpDocumentsDocumentViewer; + +}); + diff --git a/addons/mrp/static/src/js/mrp_documents_kanban_controller.js b/addons/mrp/static/src/js/mrp_documents_kanban_controller.js new file mode 100644 index 00000000..9a123dfb --- /dev/null +++ b/addons/mrp/static/src/js/mrp_documents_kanban_controller.js @@ -0,0 +1,37 @@ +odoo.define('mrp.MrpDocumentsKanbanController', function (require) { +"use strict"; + +/** + * This file defines the Controller for the MRP Documents Kanban view, which is an + * override of the KanbanController. + */ + +const MrpDocumentsControllerMixin = require('mrp.controllerMixin'); + +const KanbanController = require('web.KanbanController'); + +const MrpDocumentsKanbanController = KanbanController.extend(MrpDocumentsControllerMixin, { + events: Object.assign({}, KanbanController.prototype.events, MrpDocumentsControllerMixin.events), + custom_events: Object.assign({}, KanbanController.prototype.custom_events, MrpDocumentsControllerMixin.custom_events), + + /** + * @override + */ + init() { + this._super(...arguments); + MrpDocumentsControllerMixin.init.apply(this, arguments); + }, + /** + * Override to update the records selection. + * + * @override + */ + async reload() { + await this._super(...arguments); + await MrpDocumentsControllerMixin.reload.apply(this, arguments); + }, +}); + +return MrpDocumentsKanbanController; + +}); diff --git a/addons/mrp/static/src/js/mrp_documents_kanban_record.js b/addons/mrp/static/src/js/mrp_documents_kanban_record.js new file mode 100644 index 00000000..44558834 --- /dev/null +++ b/addons/mrp/static/src/js/mrp_documents_kanban_record.js @@ -0,0 +1,50 @@ +odoo.define('mrp.MrpDocumentsKanbanRecord', function (require) { +"use strict"; + +/** + * This file defines the KanbanRecord for the MRP Documents Kanban view. + */ + +const KanbanRecord = require('web.KanbanRecord'); + +const MrpDocumentsKanbanRecord = KanbanRecord.extend({ + events: Object.assign({}, KanbanRecord.prototype.events, { + 'click .o_mrp_download': '_onDownload', + 'click .o_kanban_previewer': '_onImageClicked', + }), + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Handles the click on the download link to save the attachment locally. + * + * @private + * @param {MouseEvent} ev + */ + _onDownload(ev) { + ev.preventDefault(); + window.location = `/web/content/${this.modelName}/${this.id}/datas?download=true`; + }, + + /** + * Handles the click on the preview image. Triggers up `_onKanbanPreview` to + * display `DocumentViewer`. + * + * @private + * @param {MouseEvent} ev + */ + _onImageClicked(ev) { + ev.preventDefault(); + ev.stopPropagation(); + this.trigger_up('kanban_image_clicked', { + recordList: [this.recordData], + recordID: this.recordData.id + }); + }, +}); + +return MrpDocumentsKanbanRecord; + +}); diff --git a/addons/mrp/static/src/js/mrp_documents_kanban_renderer.js b/addons/mrp/static/src/js/mrp_documents_kanban_renderer.js new file mode 100644 index 00000000..79ffffe7 --- /dev/null +++ b/addons/mrp/static/src/js/mrp_documents_kanban_renderer.js @@ -0,0 +1,27 @@ +odoo.define('mrp.MrpDocumentsKanbanRenderer', function (require) { +"use strict"; + +/** + * This file defines the Renderer for the MRP Documents Kanban view, which is an + * override of the KanbanRenderer. + */ + +const KanbanRenderer = require('web.KanbanRenderer'); +const MrpDocumentsKanbanRecord = require('mrp.MrpDocumentsKanbanRecord'); + +const MrpDocumentsKanbanRenderer = KanbanRenderer.extend({ + config: Object.assign({}, KanbanRenderer.prototype.config, { + KanbanRecord: MrpDocumentsKanbanRecord, + }), + /** + * @override + */ + async start() { + this.$el.addClass('o_mrp_documents_kanban_view'); + await this._super(...arguments); + }, +}); + +return MrpDocumentsKanbanRenderer; + +}); diff --git a/addons/mrp/static/src/js/mrp_should_consume.js b/addons/mrp/static/src/js/mrp_should_consume.js new file mode 100644 index 00000000..d432c5cf --- /dev/null +++ b/addons/mrp/static/src/js/mrp_should_consume.js @@ -0,0 +1,81 @@ +odoo.define('mrp.should_consume', function (require) { +"use strict"; + +const BasicFields = require('web.basic_fields'); +const FieldFloat = BasicFields.FieldFloat; +const fieldRegistry = require('web.field_registry'); +const field_utils = require('web.field_utils'); + +/** + * This widget is used to display alongside the total quantity to consume of a production order, + * the exact quantity that the worker should consume depending on the BoM. Ex: + * 2 components to make 1 finished product. + * The production order is created to make 5 finished product and the quantity producing is set to 3. + * The widget will be '3.000 / 5.000'. + */ +const MrpShouldConsume = FieldFloat.extend({ + /** + * @override + */ + init: function (parent, name, params) { + this._super.apply(this, arguments); + this.displayShouldConsume = !['done', 'draft', 'cancel'].includes(params.data.state); + this.should_consume_qty = field_utils.format.float(params.data.should_consume_qty, params.fields.should_consume_qty, this.nodeOptions); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Prefix the classic float field (this.$el) by a static value. + * + * @private + * @param {float} [value] quantity to display before the input `el` + * @param {bool} [edit] whether the field will be editable or readonly + */ + _addShouldConsume: function (value, edit=false) { + const $to_consume_container = $('<span class="o_should_consume"/>'); + if (edit) { + $to_consume_container.addClass('o_row'); + } + $to_consume_container.text(value + ' / '); + this.setElement(this.$el.wrap($to_consume_container).parent()); + }, + + /** + * @private + * @override + */ + _renderEdit: function () { + if (this.displayShouldConsume) { + if (!this.$el.text().includes('/')) { + this.$input = this.$el; + this._addShouldConsume(this.should_consume_qty, true); + } + this._prepareInput(this.$input); + } else { + this._super.apply(this); + } + }, + /** + * Resets the content to the formated value in readonly mode. + * + * @override + * @private + */ + _renderReadonly: function () { + this.$el.text(this._formatValue(this.value)); + if (this.displayShouldConsume) { + this._addShouldConsume(this.should_consume_qty); + } + }, +}); + +fieldRegistry.add('mrp_should_consume', MrpShouldConsume); + +return { + MrpShouldConsume: MrpShouldConsume, +}; + +}); diff --git a/addons/mrp/static/src/js/mrp_workorder_popover.js b/addons/mrp/static/src/js/mrp_workorder_popover.js new file mode 100644 index 00000000..7e392c09 --- /dev/null +++ b/addons/mrp/static/src/js/mrp_workorder_popover.js @@ -0,0 +1,51 @@ +odoo.define('mrp.mrp_workorder_popover', function (require) { +'use strict'; + +var PopoverWidget = require('stock.popover_widget'); +var fieldRegistry = require('web.field_registry'); +var core = require('web.core'); +var _t = core._t; + + +/** + * Link to a Char field representing a JSON: + * { + * 'replan': <REPLAN_BOOL>, // Show the replan btn + * 'color': '<COLOR_CLASS>', // Color Class of the icon (d-none to hide) + * 'infos': [ + * {'msg' : '<MESSAGE>', 'color' : '<COLOR_CLASS>'}, + * {'msg' : '<MESSAGE>', 'color' : '<COLOR_CLASS>'}, + * ... ] + * } + */ +var MrpWorkorderPopover = PopoverWidget.extend({ + popoverTemplate: 'mrp.workorderPopover', + title: _t('Scheduling Information'), + + _render: function () { + this._super.apply(this, arguments); + if (! this.$popover) { + return; + } + var self = this; + this.$popover.find('.action_replan_button').click(function (e) { + self._onReplanClick(e); + }); + }, + + _onReplanClick:function (e) { + var self = this; + this._rpc({ + model: 'mrp.workorder', + method: 'action_replan', + args: [[self.res_id]] + }).then(function () { + self.trigger_up('reload'); + }); + }, +}); + +fieldRegistry.add('mrp_workorder_popover', MrpWorkorderPopover); + +return MrpWorkorderPopover; +}); diff --git a/addons/mrp/static/src/scss/mrp_bom_report.scss b/addons/mrp/static/src/scss/mrp_bom_report.scss new file mode 100644 index 00000000..ca2e1085 --- /dev/null +++ b/addons/mrp/static/src/scss/mrp_bom_report.scss @@ -0,0 +1,50 @@ +.o_mrp_bom_report_page { + background-color: $o-view-background-color; + margin: 16px auto; + padding: 24px 16px; + // Manage expand icon + table.o_mrp_bom_expandable { + thead tr { + border-width: 2px; + border-top-style: groove; + border-bottom-style: groove; + } + tbody tr { + border-width: 1px; + border-top-style: solid; + border-bottom-style: groove; + .o_mrp_bom_unfoldable, .o_mrp_bom_foldable { + cursor: pointer; + } + } + tbody, tfoot { + & > tr > td ~ td > span { + margin-left: 10px; + } + tr { + .o_mrp_bom_no_fold { + margin-left: 18px; + } + .o_mrp_bom_unfoldable, .o_mrp_bom_foldable { + margin-left: -2px; + } + } + } + } + + @include media-breakpoint-down(sm) { + .o_mrp_bom_report_line { + & .fa-caret-down, & .fa-caret-right { + font-size: 2em; + } + } + } +} + +.o_mrp_bom_report_buttons { + @include media-breakpoint-down(sm) { + display: grid; + grid-gap: 4px; // For compatibility + gap: 4px; + } +} diff --git a/addons/mrp/static/src/scss/mrp_document_kanban_view.scss b/addons/mrp/static/src/scss/mrp_document_kanban_view.scss new file mode 100644 index 00000000..536ef77d --- /dev/null +++ b/addons/mrp/static/src/scss/mrp_document_kanban_view.scss @@ -0,0 +1,3 @@ +.o_kanban_previewer:hover { + cursor: zoom-in; +} diff --git a/addons/mrp/static/src/scss/mrp_fields.scss b/addons/mrp/static/src/scss/mrp_fields.scss new file mode 100644 index 00000000..da959565 --- /dev/null +++ b/addons/mrp/static/src/scss/mrp_fields.scss @@ -0,0 +1,11 @@ +.o_field_widget.o_embed_url_viewer{ + width: 100% !important; + iframe { + width: 100%; + height: 30rem; + border: none; + } +} +.o_should_consume{ + padding-left: 0.3em; +} diff --git a/addons/mrp/static/src/scss/mrp_gantt.scss b/addons/mrp/static/src/scss/mrp_gantt.scss new file mode 100644 index 00000000..5d6ed467 --- /dev/null +++ b/addons/mrp/static/src/scss/mrp_gantt.scss @@ -0,0 +1,19 @@ +@mixin gantt-decoration-color($color) { + background-image: linear-gradient($color, $color); + background-color: lighten($color, 10%); + &:before { + content: none; + } +} + +.o_mrp_workorder_gantt .o_gantt_view .o_gantt_row_container .o_gantt_row .o_gantt_cell .o_gantt_pill_wrapper { + div.o_gantt_pill.decoration-success { + @include gantt-decoration-color($success); + } + div.o_gantt_pill.decoration-warning { + @include gantt-decoration-color(gray('500')); + } + div.o_gantt_pill.decoration-danger { + @include gantt-decoration-color($danger); + } +} diff --git a/addons/mrp/static/src/scss/mrp_workorder_kanban.scss b/addons/mrp/static/src/scss/mrp_workorder_kanban.scss new file mode 100644 index 00000000..d2580da4 --- /dev/null +++ b/addons/mrp/static/src/scss/mrp_workorder_kanban.scss @@ -0,0 +1,10 @@ +.o_kanban_dashboard.o_kanban_view { + &.o_mrp_workorder_kanban,&.o_workcenter_kanban { + .o_kanban_group:not(.o_column_folded) { + width: 400px + $o-kanban-group-padding; + } + .o_kanban_record { + width: 400px; + } + } +}
\ No newline at end of file diff --git a/addons/mrp/static/src/xml/mrp.xml b/addons/mrp/static/src/xml/mrp.xml new file mode 100644 index 00000000..ee4eb10e --- /dev/null +++ b/addons/mrp/static/src/xml/mrp.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates> + <t t-name="mrp.button"> + <div class="o_list_buttons o_mrp_bom_report_buttons"> + <button type="button" class="btn btn-primary o_mrp_bom_print">Print</button> + <t t-if="is_variant_applied"> + <button type="button" class="btn btn-primary o_mrp_bom_print_all_variants">Print All Variants</button> + </t> + <button type="button" class="btn btn-primary o_mrp_bom_print_unfolded">Print Unfolded</button> + </div> + </t> + + <form class="form-inline" t-name="mrp.report_bom_search"> + <div class="form-group col-lg-4"> + <label>Quantity:</label> + <div class="row"> + <div class="col-lg-6"> + <input type="number" step="any" t-att-value="bom_qty" min="1" class="o_input o_mrp_bom_report_qty"/> + </div> + <div class="col-lg-6"> + <t t-if="is_uom_applied" t-esc="bom_uom_name"/> + </div> + </div> + </div> + <div t-if="is_variant_applied" class="form-group col-lg-4"> + <label>Variant:</label> + <select class="o_input o_mrp_bom_report_variants"> + <option t-foreach="variants" t-as="variant" t-att-value="variant"> + <t t-esc="variants[variant]"/> + </option> + </select> + </div> + <div t-attf-class="form-group #{is_variant_applied ? 'col-lg-4' : 'col-lg-8'}"> + <label>Report:</label> + <select class="o_input o_mrp_bom_report_type"> + <option t-att-data-type="'all'">BoM Structure & Cost</option> + <option t-att-data-type="'bom_structure'">BoM Structure</option> + </select> + </div> + </form> + + <div t-name="mrp.workorderPopover"> + <t t-foreach="infos" t-as="info"> + <i t-attf-class="fa fa-arrow-right mr-2 #{ info.color }"></i><t t-esc="info.msg"/><br/> + </t> + <button t-if="replan" class="btn btn-primary action_replan_button">Replan</button> + </div> + +</templates> diff --git a/addons/mrp/static/src/xml/mrp_document_template.xml b/addons/mrp/static/src/xml/mrp_document_template.xml new file mode 100644 index 00000000..23ea08d8 --- /dev/null +++ b/addons/mrp/static/src/xml/mrp_document_template.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + +<div t-name="MrpDocumentsKanbanView.buttons"> + <button type="button" t-attf-class="btn btn-primary o_mrp_documents_kanban_upload"> + Upload + </button> +</div> +</templates> + diff --git a/addons/mrp/static/tests/mrp_document_kanban_tests.js b/addons/mrp/static/tests/mrp_document_kanban_tests.js new file mode 100644 index 00000000..366bb1bd --- /dev/null +++ b/addons/mrp/static/tests/mrp_document_kanban_tests.js @@ -0,0 +1,172 @@ +odoo.define('mrp.document_kanban_tests', function (require) { +"use strict"; + +const MrpDocumentsKanbanView = require('mrp.MrpDocumentsKanbanView'); +const MrpDocumentsKanbanController = require('mrp.MrpDocumentsKanbanController'); +const testUtils = require('web.test_utils'); + +const createView = testUtils.createView; + +QUnit.module('Views', {}, function () { + +QUnit.module('MrpDocumentsKanbanView', { + beforeEach: function () { + this.ORIGINAL_CREATE_XHR = MrpDocumentsKanbanController.prototype._createXHR; + this.patchDocumentXHR = (mockedXHRs, customSend) => { + MrpDocumentsKanbanController.prototype._createXhr = () => { + const xhr = { + upload: new window.EventTarget(), + open() { }, + send(data) { customSend && customSend(data); }, + }; + mockedXHRs.push(xhr); + return xhr; + }; + }; + this.data = { + 'mrp.document': { + fields: { + name: {string: "Name", type: 'char', default: ' '}, + priority: {string: 'priority', type: 'selection', + selection: [['0', 'Normal'], ['1', 'Low'], ['2', 'High'], ['3', 'Very High']]}, + }, + records: [ + {id: 1, name: 'test1', priority: 2}, + {id: 4, name: 'test2', priority: 1}, + {id: 3, name: 'test3', priority: 3}, + ], + }, + }; + }, + afterEach() { + MrpDocumentsKanbanController.prototype._createXHR = this.ORIGINAL_CREATE_XHR; + }, +}, function () { + QUnit.test('MRP documents kanban basic rendering', async function (assert) { + assert.expect(6); + + const kanban = await createView({ + View: MrpDocumentsKanbanView, + model: 'mrp.document', + data: this.data, + arch: '<kanban><templates><t t-name="kanban-box">' + + '<div>' + + '<field name="name"/>' + + '</div>' + + '</t></templates></kanban>', + }); + + assert.ok(kanban, "kanban is created"); + assert.ok(kanban.$buttons.find('.o_mrp_documents_kanban_upload'), + "should have upload button in kanban buttons"); + assert.containsN(kanban, '.o_kanban_view .o_kanban_record:not(.o_kanban_ghost)', 3, + "should have 3 records in the renderer"); + // check view layout + assert.hasClass(kanban.$('.o_kanban_view'), 'o_mrp_documents_kanban_view', + "should have classname 'o_mrp_documents_kanban_view'"); + // check control panel buttons + assert.containsN(kanban, '.o_cp_buttons .btn-primary', 1, + "should have only 1 primary button i.e. Upload button"); + assert.strictEqual(kanban.$('.o_cp_buttons .btn-primary:first').text().trim(), 'Upload', + "should have a primary 'Upload' button"); + + kanban.destroy(); + }); + + QUnit.test('mrp: upload multiple files', async function (assert) { + assert.expect(4); + + const file1 = await testUtils.file.createFile({ + name: 'text1.txt', + content: 'hello, world', + contentType: 'text/plain', + }); + const file2 = await testUtils.file.createFile({ + name: 'text2.txt', + content: 'hello, world', + contentType: 'text/plain', + }); + const file3 = await testUtils.file.createFile({ + name: 'text3.txt', + content: 'hello, world', + contentType: 'text/plain', + }); + + const mockedXHRs = []; + this.patchDocumentXHR(mockedXHRs, data => assert.step('xhrSend')); + + const kanban = await createView({ + View: MrpDocumentsKanbanView, + model: 'mrp.document', + data: this.data, + arch: '<kanban><templates><t t-name="kanban-box">' + + '<div>' + + '<field name="name"/>' + + '</div>' + + '</t></templates></kanban>', + }); + + kanban.trigger_up('upload_file', {files: [file1]}); + await testUtils.nextTick(); + assert.verifySteps(['xhrSend']); + + kanban.trigger_up('upload_file', {files: [file2, file3]}); + await testUtils.nextTick(); + assert.verifySteps(['xhrSend']); + + kanban.destroy(); + }); + + QUnit.test('mrp: upload progress bars', async function (assert) { + assert.expect(4); + + const file1 = await testUtils.file.createFile({ + name: 'text1.txt', + content: 'hello, world', + contentType: 'text/plain', + }); + + const mockedXHRs = []; + this.patchDocumentXHR(mockedXHRs, data => assert.step('xhrSend')); + + const kanban = await createView({ + View: MrpDocumentsKanbanView, + model: 'mrp.document', + data: this.data, + arch: '<kanban><templates><t t-name="kanban-box">' + + '<div>' + + '<field name="name"/>' + + '</div>' + + '</t></templates></kanban>', + }); + + kanban.trigger_up('upload_file', {files: [file1]}); + await testUtils.nextTick(); + assert.verifySteps(['xhrSend']); + + const progressEvent = new Event('progress', { bubbles: true }); + progressEvent.loaded = 250000000; + progressEvent.total = 500000000; + progressEvent.lengthComputable = true; + mockedXHRs[0].upload.dispatchEvent(progressEvent); + assert.strictEqual( + kanban.$('.o_file_upload_progress_text_left').text(), + "Uploading... (50%)", + "the current upload progress should be at 50%" + ); + + progressEvent.loaded = 350000000; + mockedXHRs[0].upload.dispatchEvent(progressEvent); + assert.strictEqual( + kanban.$('.o_file_upload_progress_text_right').text(), + "(350/500Mb)", + "the current upload progress should be at (350/500Mb)" + ); + + kanban.destroy(); + }); +}); + +}); + +}); diff --git a/addons/mrp/static/tests/mrp_tests.js b/addons/mrp/static/tests/mrp_tests.js new file mode 100644 index 00000000..f05a5b42 --- /dev/null +++ b/addons/mrp/static/tests/mrp_tests.js @@ -0,0 +1,133 @@ +odoo.define('mrp.tests', function (require) { +"use strict"; + +var FormView = require('web.FormView'); +var testUtils = require("web.test_utils"); + +var createView = testUtils.createView; + +QUnit.module('mrp', { + beforeEach: function () { + this.data = { + partner: { + fields: { + state: { + string: "State", + type: "selection", + selection: [['waiting', 'Waiting'], ['chilling', 'Chilling']], + }, + duration: {string: "Duration", type: "float"}, + }, + records: [{ + id: 1, + state: 'waiting', + duration: 6000, + }], + onchanges: {}, + }, + }; + }, +}, function () { + + QUnit.test("bullet_state: basic rendering", async function (assert) { + assert.expect(2); + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + res_id: 1, + arch: + '<form>' + + '<field name="state" widget="bullet_state" options="{\'classes\': {\'waiting\': \'danger\'}}"/>' + + '</form>', + }); + + assert.strictEqual(form.$('.o_field_widget').text(), "Waiting Materials", + "the widget should be correctly named"); + assert.containsOnce(form, '.o_field_widget .badge-danger', + "the badge should be danger"); + + form.destroy(); + }); + + QUnit.test("mrp_time_counter: basic rendering", async function (assert) { + assert.expect(2); + var data = { + foo: { + fields: { duration: { string: "Duration", type: "float" } }, + records: [{id: 1, duration:150.5}] + }, + }; + var form = await createView({ + View: FormView, + model: 'foo', + data: data, + res_id: 1, + arch: + '<form>' + + '<field name="duration" widget="mrp_time_counter"/>' + + '</form>', + mockRPC: function (route, args) { + if (args.method === 'search_read' && args.model === 'mrp.workcenter.productivity') { + assert.ok(true, "the widget should fetch the mrp.workcenter.productivity"); + return Promise.resolve([]); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual(form.$('.o_field_widget[name="duration"]').text(), "150:30", + "the timer should be correctly set"); + + form.destroy(); + }); + + QUnit.test("embed_viewer rendering in form view", async function (assert) { + assert.expect(8); + var data = { + foo: { + fields: { char_url: { string: "URL", type: "char" } }, + records: [{ id: 1 }] + }, + }; + + var form = await createView({ + View: FormView, + model: 'foo', + data: data, + arch: + '<form>' + + '<field name="char_url" widget="embed_viewer"/>' + + '</form>', + res_id: 1, + mockRPC: function (route) { + if (route === ('http://example.com')) { + return Promise.resolve(); + } + return this._super.apply(this, arguments); + } + }); + + assert.isNotVisible(form.$('iframe.o_embed_iframe'), "there should be an invisible iframe readonly mode"); + assert.strictEqual(_.has(form.$('iframe.o_embed_iframe')[0].attributes, "src"), false, + "src attribute is not set if there are no values"); + await testUtils.form.clickEdit(form); + assert.isNotVisible(form.$('iframe.o_embed_iframe'), "there should be an invisible iframe in edit mode"); + await testUtils.fields.editAndTrigger(form.$('.o_field_char'), 'http://example.com', ['input', 'change', 'focusout']); + assert.strictEqual(form.$('iframe.o_embed_iframe').attr('src'), 'http://example.com', + "src should updated on the iframe"); + assert.isVisible(form.$('iframe.o_embed_iframe'), "there should be a visible iframe in edit mode"); + await testUtils.form.clickSave(form); + assert.isVisible(form.$('iframe.o_embed_iframe'), "there should be a visible iframe in readonly mode"); + assert.strictEqual(form.$('iframe.o_embed_iframe').attr('data-src'), 'http://example.com', + "should have updated src in readonly mode"); + + // In readonly mode, we are not displaying the URL, only iframe will be there. + assert.strictEqual(form.$('.iframe.o_embed_iframe').siblings().length, 0, + "there shouldn't be any siblings of iframe in readonly mode"); + + form.destroy(); + }); +}); +}); diff --git a/addons/mrp/static/xls/mrp_bom.xls b/addons/mrp/static/xls/mrp_bom.xls Binary files differnew file mode 100644 index 00000000..4114d545 --- /dev/null +++ b/addons/mrp/static/xls/mrp_bom.xls |
