mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 09:52:01 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
|
|
@ -0,0 +1,6 @@
|
|||
$(function () {
|
||||
$('input.o-purchase-datetimepicker').datetimepicker();
|
||||
$('input.o-purchase-datetimepicker').on("hide.datetimepicker", function () {
|
||||
$(this).parents('form').submit();
|
||||
});
|
||||
})
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
odoo.define('purchase.PurchasePortalSidebar', function (require) {
|
||||
'use strict';
|
||||
|
||||
var publicWidget = require('web.public.widget');
|
||||
var PortalSidebar = require('portal.PortalSidebar');
|
||||
|
||||
publicWidget.registry.PurchasePortalSidebar = PortalSidebar.extend({
|
||||
selector: '.o_portal_purchase_sidebar',
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
init: function (parent, options) {
|
||||
this._super.apply(this, arguments);
|
||||
this.authorizedTextTag = ['em', 'b', 'i', 'u'];
|
||||
this.spyWatched = $('body[data-target=".navspy"]');
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var def = this._super.apply(this, arguments);
|
||||
var $spyWatcheElement = this.$el.find('[data-id="portal_sidebar"]');
|
||||
this._setElementId($spyWatcheElement);
|
||||
// Nav Menu ScrollSpy
|
||||
this._generateMenu();
|
||||
return def;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* create an unique id and added as a attribute of spyWatched element
|
||||
*
|
||||
* @private
|
||||
* @param {string} prefix
|
||||
* @param {Object} $el
|
||||
*
|
||||
*/
|
||||
_setElementId: function (prefix, $el) {
|
||||
var id = _.uniqueId(prefix);
|
||||
this.spyWatched.find($el).attr('id', id);
|
||||
return id;
|
||||
},
|
||||
/**
|
||||
* generate the new spy menu
|
||||
*
|
||||
* @private
|
||||
*
|
||||
*/
|
||||
_generateMenu: function () {
|
||||
var self = this,
|
||||
lastLI = false,
|
||||
lastUL = null,
|
||||
$bsSidenav = this.$el.find('.bs-sidenav');
|
||||
|
||||
$("#quote_content [id^=quote_header_], #quote_content [id^=quote_]", this.spyWatched).attr("id", "");
|
||||
_.each(this.spyWatched.find("#quote_content h2, #quote_content h3"), function (el) {
|
||||
var id, text;
|
||||
switch (el.tagName.toLowerCase()) {
|
||||
case "h2":
|
||||
id = self._setElementId('quote_header_', el);
|
||||
text = self._extractText($(el));
|
||||
if (!text) {
|
||||
break;
|
||||
}
|
||||
lastLI = $("<li class='nav-item'>").append($('<a class="nav-link" style="max-width: 200px;" href="#' + id + '"/>').text(text)).appendTo($bsSidenav);
|
||||
lastUL = false;
|
||||
break;
|
||||
case "h3":
|
||||
id = self._setElementId('quote_', el);
|
||||
text = self._extractText($(el));
|
||||
if (!text) {
|
||||
break;
|
||||
}
|
||||
if (lastLI) {
|
||||
if (!lastUL) {
|
||||
lastUL = $("<ul class='nav flex-column'>").appendTo(lastLI);
|
||||
}
|
||||
$("<li class='nav-item'>").append($('<a class="nav-link" style="max-width: 200px;" href="#' + id + '"/>').text(text)).appendTo(lastUL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
el.setAttribute('data-anchor', true);
|
||||
});
|
||||
this.trigger_up('widgets_start_request', {$target: $bsSidenav});
|
||||
},
|
||||
/**
|
||||
* extract text of menu title for sidebar
|
||||
*
|
||||
* @private
|
||||
* @param {Object} $node
|
||||
*
|
||||
*/
|
||||
_extractText: function ($node) {
|
||||
var self = this;
|
||||
var rawText = [];
|
||||
_.each($node.contents(), function (el) {
|
||||
var current = $(el);
|
||||
if ($.trim(current.text())) {
|
||||
var tagName = current.prop("tagName");
|
||||
if (_.isUndefined(tagName) || (!_.isUndefined(tagName) && _.contains(self.authorizedTextTag, tagName.toLowerCase()))) {
|
||||
rawText.push($.trim(current.text()));
|
||||
}
|
||||
}
|
||||
});
|
||||
return rawText.join(' ');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
odoo.define('purchase.ToasterButton', function (require) {
|
||||
'use strict';
|
||||
|
||||
const widgetRegistry = require('web.widget_registry');
|
||||
const Widget = require('web.Widget');
|
||||
|
||||
|
||||
const ToasterButton = Widget.extend({
|
||||
template: 'purchase.ToasterButton',
|
||||
events: Object.assign({}, Widget.prototype.events, {
|
||||
'click .fa-info-circle': '_onClickButton',
|
||||
}),
|
||||
|
||||
init: function (parent, data, node) {
|
||||
this._super(...arguments);
|
||||
this.button_name = node.attrs.button_name;
|
||||
this.title = node.attrs.title;
|
||||
this.id = data.res_id;
|
||||
this.model = data.model;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
_onClickButton: function (ev) {
|
||||
this._rpc({
|
||||
method: this.button_name,
|
||||
model: this.model,
|
||||
args: [[this.id]],
|
||||
}).then(res => {
|
||||
if (res) {
|
||||
this.displayNotification({ message: res.toast_message });
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
widgetRegistry.add('toaster_button', ToasterButton);
|
||||
|
||||
return ToasterButton;
|
||||
});
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
odoo.define('purchase.purchase_steps', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
|
||||
var PurchaseAdditionalTourSteps = core.Class.extend({
|
||||
|
||||
_get_purchase_stock_steps: function () {
|
||||
return [
|
||||
{
|
||||
auto: true, // Useless final step to trigger congratulation message
|
||||
trigger: ".o_purchase_order",
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return PurchaseAdditionalTourSteps;
|
||||
|
||||
});
|
||||
|
||||
odoo.define('purchase.tour', function(require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var tour = require('web_tour.tour');
|
||||
|
||||
var _t = core._t;
|
||||
var PurchaseAdditionalTourSteps = require('purchase.purchase_steps');
|
||||
|
||||
tour.register('purchase_tour' , {
|
||||
url: "/web",
|
||||
sequence: 40,
|
||||
}, [tour.stepUtils.showAppsMenuItem(), {
|
||||
trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]',
|
||||
content: _t("Let's try the Purchase app to manage the flow from purchase to reception and invoice control."),
|
||||
position: 'right',
|
||||
edition: 'community'
|
||||
}, {
|
||||
trigger: '.o_app[data-menu-xmlid="purchase.menu_purchase_root"]',
|
||||
content: _t("Let's try the Purchase app to manage the flow from purchase to reception and invoice control."),
|
||||
position: 'bottom',
|
||||
edition: 'enterprise'
|
||||
}, {
|
||||
trigger: ".o_list_button_add",
|
||||
extra_trigger: ".o_purchase_order",
|
||||
content: _t("Let's create your first request for quotation."),
|
||||
position: "bottom",
|
||||
}, {
|
||||
trigger: ".o_form_editable .o_field_res_partner_many2one[name='partner_id']",
|
||||
extra_trigger: ".o_purchase_order",
|
||||
content: _t("Search a vendor name, or create one on the fly."),
|
||||
position: "bottom",
|
||||
run: function (actions) {
|
||||
actions.text("Azure Interior", this.$anchor.find("input"));
|
||||
},
|
||||
}, {
|
||||
trigger: ".ui-menu-item > a:contains('Azure Interior')",
|
||||
auto: true,
|
||||
in_modal: false,
|
||||
}, {
|
||||
trigger: ".o_field_x2many_list_row_add > a",
|
||||
content: _t("Add some products or services to your quotation."),
|
||||
position: "bottom",
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=product_id], .o_field_widget[name=product_template_id]",
|
||||
extra_trigger: ".o_purchase_order",
|
||||
content: _t("Select a product, or create a new one on the fly."),
|
||||
position: "right",
|
||||
run: function (actions) {
|
||||
var $input = this.$anchor.find('input');
|
||||
actions.text("DESK0001", $input.length === 0 ? this.$anchor : $input);
|
||||
// fake keydown to trigger search
|
||||
var keyDownEvent = jQuery.Event("keydown");
|
||||
keyDownEvent.which = 42;
|
||||
this.$anchor.trigger(keyDownEvent);
|
||||
},
|
||||
}, {
|
||||
trigger: "a:contains('DESK0001')",
|
||||
auto: true,
|
||||
}, {
|
||||
trigger: "td[name='name'][data-tooltip*='DESK0001']",
|
||||
auto: true,
|
||||
run: function () {} // wait for product creation
|
||||
}, {
|
||||
trigger: "div.o_field_widget[name='product_qty'] input ",
|
||||
extra_trigger: ".o_purchase_order",
|
||||
content: _t("Indicate the product quantity you want to order."),
|
||||
position: "right",
|
||||
run: 'text 12.0'
|
||||
},
|
||||
...tour.stepUtils.statusbarButtonsSteps('Send by Email', _t("Send the request for quotation to your vendor."), ".o_statusbar_buttons button[name='action_rfq_send']"),
|
||||
{
|
||||
trigger: ".modal-content",
|
||||
auto: true,
|
||||
run: function(actions){
|
||||
// Check in case user must add email to vendor
|
||||
var $input = $(".modal-content input[name='email']");
|
||||
if ($input.length) {
|
||||
actions.text("agrolait@example.com", $input);
|
||||
actions.click($(".modal-footer button"));
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: ".modal-footer button[name='action_send_mail']",
|
||||
extra_trigger: ".modal-footer button[name='action_send_mail']",
|
||||
content: _t("Send the request for quotation to your vendor."),
|
||||
position: "left",
|
||||
run: 'click',
|
||||
}, {
|
||||
content: "Select price",
|
||||
trigger: 'tbody tr.o_data_row .o_list_number[name="price_unit"]',
|
||||
}, {
|
||||
trigger: 'tbody tr.o_data_row .o_list_number[name="price_unit"] input',
|
||||
extra_trigger: ".o_purchase_order",
|
||||
content: _t("Once you get the price from the vendor, you can complete the purchase order with the right price."),
|
||||
position: "right",
|
||||
run: 'text 200.00'
|
||||
}, {
|
||||
auto: true,
|
||||
trigger: ".o_purchase_order",
|
||||
run: 'click',
|
||||
}, ...tour.stepUtils.statusbarButtonsSteps('Confirm Order', _t("Confirm your purchase.")),
|
||||
...new PurchaseAdditionalTourSteps()._get_purchase_stock_steps(),
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/** @odoo-module */
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component } = owl;
|
||||
|
||||
class ButtonWithNotification extends Component {
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
this.notification = useService("notification");
|
||||
}
|
||||
|
||||
async onClick() {
|
||||
const result = await this.orm.call(this.props.record.resModel, this.props.method, [this.props.record.resId]);
|
||||
const message = result.toast_message;
|
||||
this.notification.add(message, { type: "success" });
|
||||
}
|
||||
}
|
||||
ButtonWithNotification.template = "purchase.ButtonWithNotification";
|
||||
ButtonWithNotification.extractProps = ({ attrs }) => {
|
||||
return {
|
||||
method: attrs.button_name,
|
||||
title: attrs.title,
|
||||
};
|
||||
};
|
||||
|
||||
registry.category("view_widgets").add("toaster_button", ButtonWithNotification);
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<template>
|
||||
|
||||
<t t-name="purchase.ButtonWithNotification" owl="1">
|
||||
<button t-on-click="onClick" t-att-name="props.method" type="button" class="btn oe_inline btn-link" t-att-title="props.title">
|
||||
<i class="fa fa-fw o_button_icon fa-info-circle"></i>
|
||||
</button>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/** @odoo-module */
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component, onWillStart } = owl;
|
||||
|
||||
export class PurchaseDashBoard extends Component {
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
this.action = useService("action");
|
||||
|
||||
onWillStart(async () => {
|
||||
this.purchaseData = await this.orm.call(
|
||||
"purchase.order",
|
||||
"retrieve_dashboard",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method clears the current search query and activates
|
||||
* the filters found in `filter_name` attibute from button pressed
|
||||
*/
|
||||
setSearchContext(ev) {
|
||||
let filter_name = ev.currentTarget.getAttribute("filter_name");
|
||||
let filters = filter_name.split(',');
|
||||
let searchItems = this.env.searchModel.getSearchItems((item) => filters.includes(item.name));
|
||||
this.env.searchModel.query = [];
|
||||
for (const item of searchItems){
|
||||
this.env.searchModel.toggleSearchItem(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PurchaseDashBoard.template = 'purchase.PurchaseDashboard'
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-name="purchase.PurchaseDashboard" owl="1">
|
||||
<div class="o_purchase_dashboard container-fluid py-4 border-bottom bg-white">
|
||||
<div class="row justify-content-between gap-3 gap-lg-0">
|
||||
<div class="col-12 col-lg-5 col-xl-5 col-xxl-4 flex-grow-1 flex-lg-grow-0 flex-shrink-0">
|
||||
<div class="grid gap-4">
|
||||
<div class="g-col-3 g-col-sm-2 d-flex align-items-center py-2 justify-content-end text-end justify-content-lg-start text-lg-start text-break">
|
||||
All RFQs
|
||||
</div>
|
||||
<div class="g-col-9 g-col-sm-10 grid gap-1">
|
||||
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="All Draft RFQs" filter_name="draft_rfqs">
|
||||
<a href="#" class="btn btn-primary w-100 h-100 border-0 rounded-0 text-capitalize text-break fw-normal">
|
||||
<div class="fs-2" t-out="purchaseData['all_to_send']"/>To Send
|
||||
</a>
|
||||
</div>
|
||||
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="All Waiting RFQs" filter_name="waiting_rfqs">
|
||||
<a href="#" class="btn btn-primary w-100 h-100 border-0 rounded-0 text-capitalize text-break fw-normal">
|
||||
<div class="fs-2" t-out="purchaseData['all_waiting']"/>Waiting
|
||||
</a>
|
||||
</div>
|
||||
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="All Late RFQs" filter_name="late_rfqs">
|
||||
<a href="#" class="btn btn-primary w-100 h-100 border-0 rounded-0 text-capitalize text-break fw-normal">
|
||||
<div class="fs-2" t-out="purchaseData['all_late']"/>Late
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-4">
|
||||
<div class="g-col-3 g-col-sm-2 d-flex align-items-center py-2 justify-content-end text-end justify-content-lg-start text-lg-start text-break">
|
||||
My RFQs
|
||||
</div>
|
||||
<div class="g-col-9 g-col-sm-10 grid gap-2">
|
||||
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="My Draft RFQs" filter_name="draft_rfqs,my_purchases">
|
||||
<a href="#" class="btn btn-light d-flex align-items-center w-100 h-100 p-0 border-0 bg-100 fw-normal">
|
||||
<div class="w-100 p-2" t-out="purchaseData['my_to_send']"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="My Waiting RFQs" filter_name="waiting_rfqs,my_purchases">
|
||||
<a href="#" class="btn btn-light d-flex align-items-center w-100 h-100 p-0 border-0 bg-100 fw-normal">
|
||||
<div class="w-100 p-2" t-out="purchaseData['my_waiting']"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="g-col-4 p-0" t-on-click="setSearchContext" title="My Late RFQs" filter_name="late_rfqs,my_purchases">
|
||||
<a href="#" class="btn btn-light d-flex align-items-center w-100 h-100 p-0 border-0 bg-100 fw-normal">
|
||||
<div class="w-100 p-2" t-out="purchaseData['my_late']"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-7 col-xl-6 col-xxl-5 flex-shrink-0">
|
||||
<div class="d-flex flex-column justify-content-between gap-2 h-100">
|
||||
<div class="grid gap-2 h-100">
|
||||
<div class="g-col-6 g-col-md-6 grid gap-1 gap-md-4">
|
||||
<div class="g-col-12 g-col-sm-4 g-col-lg-6 d-flex align-items-center justify-content-center text-center justify-content-md-end text-md-end mt-4 mt-sm-0 text-break">
|
||||
Avg Order Value
|
||||
</div>
|
||||
<div class="g-col-12 g-col-sm-8 g-col-lg-5 d-flex align-items-center justify-content-center py-2 bg-light">
|
||||
<span><t t-out="purchaseData['all_avg_order_value']"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="g-col-6 g-col-md-6 grid gap-1 gap-md-4">
|
||||
<div class="g-col-12 g-col-sm-4 g-col-lg-6 d-flex align-items-center py-2 justify-content-center text-center justify-content-md-end text-md-end mt-4 mt-sm-0 text-break">
|
||||
Purchased Last 7 Days
|
||||
</div>
|
||||
<div class="g-col-12 g-col-sm-8 g-col-lg-6 d-flex align-items-center justify-content-center py-2 bg-light">
|
||||
<span><t t-out="purchaseData['all_total_last_7_days']"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2 h-100">
|
||||
<div class="g-col-6 g-col-md-6 grid gap-1 gap-md-4">
|
||||
<div class="g-col-12 g-col-sm-4 g-col-lg-6 d-flex align-items-center justify-content-center text-center justify-content-md-end text-md-end mt-4 mt-sm-0 text-break">
|
||||
Lead Time to Purchase
|
||||
</div>
|
||||
<div class="g-col-12 g-col-sm-8 g-col-lg-5 d-flex align-items-center justify-content-center py-2 bg-light">
|
||||
<span><t t-out="purchaseData['all_avg_days_to_purchase']"/>  Days</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="g-col-6 g-col-md-6 grid gap-1 gap-md-4">
|
||||
<div class="g-col-12 g-col-md-4 g-col-sm-4 g-col-lg-6 d-flex align-items-center justify-content-center text-center justify-content-md-end text-md-end mt-4 mt-sm-0 text-break">
|
||||
RFQs Sent Last 7 Days
|
||||
</div>
|
||||
<div class="g-col-12 g-col-sm-8 g-col-lg-6 d-flex align-items-center justify-content-center py-2 bg-light">
|
||||
<span><t t-out="purchaseData['all_sent_rfqs']"/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { kanbanView } from '@web/views/kanban/kanban_view';
|
||||
import { KanbanRenderer } from '@web/views/kanban/kanban_renderer';
|
||||
import { PurchaseDashBoard } from '@purchase/views/purchase_dashboard';
|
||||
|
||||
|
||||
export class PurchaseDashBoardKanbanRenderer extends KanbanRenderer {};
|
||||
|
||||
PurchaseDashBoardKanbanRenderer.template = 'purchase.PurchaseKanbanView';
|
||||
PurchaseDashBoardKanbanRenderer.components= Object.assign({}, KanbanRenderer.components, {PurchaseDashBoard})
|
||||
|
||||
export const PurchaseDashBoardKanbanView = {
|
||||
...kanbanView,
|
||||
Renderer: PurchaseDashBoardKanbanRenderer,
|
||||
};
|
||||
|
||||
registry.category("views").add("purchase_dashboard_kanban", PurchaseDashBoardKanbanView);
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<templates>
|
||||
<t t-name="purchase.PurchaseKanbanView" t-inherit="web.KanbanRenderer" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//div[hasclass('o_kanban_renderer')]" position="before">
|
||||
<PurchaseDashBoard />
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { listView } from "@web/views/list/list_view";
|
||||
import { ListRenderer } from "@web/views/list/list_renderer";
|
||||
import { PurchaseDashBoard } from '@purchase/views/purchase_dashboard';
|
||||
|
||||
export class PurchaseDashBoardRenderer extends ListRenderer {};
|
||||
|
||||
PurchaseDashBoardRenderer.template = 'purchase.PurchaseListView';
|
||||
PurchaseDashBoardRenderer.components= Object.assign({}, ListRenderer.components, {PurchaseDashBoard})
|
||||
|
||||
export const PurchaseDashBoardListView = {
|
||||
...listView,
|
||||
Renderer: PurchaseDashBoardRenderer,
|
||||
};
|
||||
|
||||
registry.category("views").add("purchase_dashboard_list", PurchaseDashBoardListView);
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<templates>
|
||||
<t t-name="purchase.PurchaseListView" t-inherit="web.ListRenderer" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//div[hasclass('o_list_renderer')]" position="before">
|
||||
<PurchaseDashBoard />
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<template>
|
||||
|
||||
<t t-name="purchase.ToasterButton">
|
||||
<button t-att-name="widget.button_name" type="button" class="btn oe_inline btn-link" t-att-title="widget.title">
|
||||
<i class="fa fa-fw o_button_icon fa-info-circle"></i>
|
||||
</button>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
Loading…
Add table
Add a link
Reference in a new issue