init: Euro-Office Odoo 16.0 modules
Based on onlyoffice_odoo by Ascensio System SIA (ONLYOFFICE, LGPL-3). Rebranded and adapted for Euro-Office by bring.out d.o.o. Modules: - eurooffice_odoo: base integration - eurooffice_odoo_templates: document templates - eurooffice_odoo_oca_dms: OCA DMS integration (replaces Enterprise documents) All references renamed: onlyoffice -> eurooffice, ONLYOFFICE -> Euro-Office. Original copyright notices preserved.
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 422 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
|
@ -0,0 +1,23 @@
|
|||
<svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1648_12465)">
|
||||
<rect width="144" height="144" rx="8" fill="#F5F5F5"/>
|
||||
<path opacity="0.12" d="M24.6858 31.8856L41 119L96.5 119L117 109L123.419 112.105L43.3679 192.46L-46.2607 102.831L24.6858 31.8856Z" fill="#808080"/>
|
||||
<path d="M25 34C25 32.3431 26.3431 31 28 31H37V113H28C26.3431 113 25 111.657 25 110V34Z" fill="white"/>
|
||||
<path d="M47 31H28C26.3431 31 25 32.3431 25 34V110C25 111.657 26.3431 113 28 113H47" stroke="#666666" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M37 30C37 28.3431 38.3431 27 40 27H83.5882L109 52.0909V116C109 117.657 107.657 119 106 119H40C38.3431 119 37 117.657 37 116V30Z" fill="white"/>
|
||||
<path d="M78.1765 117H40C38.3431 117 37 115.657 37 114V30C37 28.3431 38.3431 27 40 27H82.2941L107 51.5455V76.0909" stroke="#666666" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M82.2856 26.7429L106.971 51.4286H85.2856C83.6288 51.4286 82.2856 50.0855 82.2856 48.4286V26.7429Z" fill="#666666"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.602 120.161L87.3729 113.121C86.0756 112.507 86.0756 111.557 87.3729 110.998L92.6747 108.54L102.545 113.121C103.843 113.736 105.929 113.736 107.17 113.121L117.041 108.54L122.343 110.998C123.64 111.613 123.64 112.562 122.343 113.121L107.114 120.161C105.929 120.72 103.843 120.72 102.602 120.161Z" fill="#FF6F3D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.602 111.501L87.3729 104.461C86.0756 103.846 86.0756 102.896 87.3729 102.338L92.5619 99.9351L102.602 104.573C103.899 105.187 105.986 105.187 107.227 104.573L117.266 99.9351L122.456 102.338C123.753 102.952 123.753 103.902 122.456 104.461L107.227 111.501C105.929 112.115 103.843 112.115 102.602 111.501Z" fill="#95C038"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.602 103.064L87.3729 96.024C86.0756 95.4094 86.0756 94.4596 87.3729 93.9008L102.602 86.8609C103.899 86.2463 105.986 86.2463 107.227 86.8609L122.456 93.9008C123.753 94.5154 123.753 95.4653 122.456 96.024L107.227 103.064C105.929 103.623 103.843 103.623 102.602 103.064Z" fill="#5DC0E8"/>
|
||||
<path d="M78 66C78 64.8954 78.9514 64 80.125 64H92.875C94.0486 64 95 64.8954 95 66C95 67.1046 94.0486 68 92.875 68H80.125C78.9514 68 78 67.1046 78 66Z" fill="#666666"/>
|
||||
<path d="M49 78C49 76.8954 49.9361 76 51.0909 76H92.9091C94.0639 76 95 76.8954 95 78C95 79.1046 94.0639 80 92.9091 80H51.0909C49.9361 80 49 79.1046 49 78Z" fill="#666666"/>
|
||||
<path d="M49 90C49 88.8954 49.9253 88 51.0667 88H77.9333C79.0747 88 80 88.8954 80 90C80 91.1046 79.0747 92 77.9333 92H51.0667C49.9253 92 49 91.1046 49 90Z" fill="#666666"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M66 51H53V64H66V51ZM53.2 47C50.8804 47 49 48.8804 49 51.2V63.8C49 66.1196 50.8804 68 53.2 68H65.8C68.1196 68 70 66.1196 70 63.8V51.2C70 48.8804 68.1196 47 65.8 47H53.2Z" fill="#666666"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1648_12465">
|
||||
<rect width="144" height="144" rx="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -0,0 +1,145 @@
|
|||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">
|
||||
Automate form creation with inserting fields from Odoo in templates
|
||||
</h2>
|
||||
<div class="oe_span12">
|
||||
<span class="text-center">
|
||||
<p class="oe_mt16">
|
||||
Work with fillable templates in Odoo using Euro-Office. Create templates based
|
||||
on the data and fields available in Odoo, fill them out and print with several
|
||||
clicks.
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container mt-5">
|
||||
<div class="oe_row">
|
||||
<div class="oe_span12">
|
||||
<h6>Edit the existing templates</h6>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<p>
|
||||
<b>Please note</b>: Euro-Office demo templates will only be added to the Odoo
|
||||
modules that are already installed. That's why we strongly recommend installing
|
||||
Euro-Office Templates after installing other Odoo modules such as CRM, Sales,
|
||||
Calendar, etc.
|
||||
</p>
|
||||
<p>
|
||||
Launch the Euro-Office Templates app to open the page with the existing form
|
||||
templates. Each template has a tag indicating which Odoo module the template was
|
||||
created for.
|
||||
</p>
|
||||
<p>
|
||||
When you click the Edit button in the file context menu, a form template will
|
||||
open in the Euro-Office editor. The left panel dynamically displays the fields
|
||||
from Odoo that can be linked to the Euro-Office form fields. The Odoo field ID is
|
||||
written in the Key field of the editor in the right panel.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_screenshot">
|
||||
<img src="edit_templates.png" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container mt-5">
|
||||
<div class="oe_row">
|
||||
<div class="oe_span12">
|
||||
<h6>Create new templates</h6>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<p>
|
||||
To create a new template, click the Create&Upload button. In the pop-up window,
|
||||
specify the template name and select the Odoo module to which the template
|
||||
belongs.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_screenshot">
|
||||
<img src="create_templates.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<p class="oe_mt8">
|
||||
You can upload any existing form template from your local drive (using the
|
||||
Upload your file button), or save a new blank template (using the Create button
|
||||
in the upper right corner).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container mt-5">
|
||||
<div class="oe_row">
|
||||
<div class="oe_span12">
|
||||
<h6>Work with ready templates</h6>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<p>
|
||||
Several standard templates are available when installing the Euro-Office Templates app, but certain Odoo modules
|
||||
must be installed first. Without these modules, the templates won't appear in the Euro-Office Templates module.
|
||||
After installing the required modules, you can export templates by navigating to Settings -> Euro-Office ->
|
||||
Manage Templates. Select the templates you want in the pop-up window and click Export to add them to the
|
||||
Euro-Office Templates module.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_screenshot">
|
||||
<img src="work_with_templates.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<p class="oe_mt8">
|
||||
Next, a window appears with a list of templates available for this section.
|
||||
Select the required template so that it is filled with the data taken from the
|
||||
linked Odoo page (deal, employee card, etc.).
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<p class="oe_mt8">
|
||||
You can also preview the form template a user wants to fill out by clicking the Preview button in the form
|
||||
templates list. This will display a preview of the template, but no data from Odoo will be included at this
|
||||
stage.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<p class="oe_mt8">
|
||||
To print multiple records using a template, select the desired items in the list view, then click Action ->
|
||||
Print with Euro-Office. In the pop-up window, click Print, and a ZIP file containing the filled PDFs will be
|
||||
downloaded to your device.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container mt-5">
|
||||
<div class="oe_row">
|
||||
<div class="oe_span12">
|
||||
<h6>Access rights</h6>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<p>There are two types of roles for this app:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>User:</b> can print templates and open them for viewing. Users cannot
|
||||
create new templates and cannot edit the current ones.
|
||||
</li>
|
||||
<li><b>Administrator:</b> can open, create, edit and print templates.</li>
|
||||
</ul>
|
||||
<p>
|
||||
The role type is specified in the user profile. By default, all users are
|
||||
assigned the User role.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span12">
|
||||
<div class="oe_demo oe_screenshot">
|
||||
<img src="access_rights.png" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
|
@ -0,0 +1,4 @@
|
|||
.o_eurooffice_kanban_record_selected {
|
||||
border-color: #017e84;
|
||||
box-shadow: 0 0 0 2px #017e84;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
.o-eurooffice-template {
|
||||
display: flex;
|
||||
|
||||
.o-eurooffice-template-fields {
|
||||
width: 320px;
|
||||
height: 100%;
|
||||
background-color: var(--body-bg) !important;
|
||||
font-family: Arial !important;
|
||||
font-weight: 400 !important;
|
||||
font-size: 11px !important;
|
||||
line-height: 14px !important;
|
||||
letter-spacing: 2% !important;
|
||||
|
||||
.o-eurooffice-fields-title {
|
||||
display: flex;
|
||||
height: 54px;
|
||||
padding: 0 12px;
|
||||
align-items: center;
|
||||
|
||||
font-family: Arial;
|
||||
font-weight: 700;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
letter-spacing: 2%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.o-eurooffice-template-separator {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
border-color: var(--gray-300);
|
||||
}
|
||||
}
|
||||
|
||||
.o-eurooffice-template-editor {
|
||||
width: calc(100% - 320px);
|
||||
height: 100%;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
.o-eurooffice-fields-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 12px;
|
||||
padding: 12px 16px;
|
||||
justify-content: center;
|
||||
|
||||
.o-eurooffice-options-help {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.o-eurooffice-options-title {
|
||||
align-items: center;
|
||||
|
||||
font-family: Arial;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
letter-spacing: 2%;
|
||||
}
|
||||
|
||||
.o-eurooffice-options-search {
|
||||
position: relative;
|
||||
background-color: var(--o-cc1-btn-primary-text);
|
||||
|
||||
.o-eurooffice-search-field {
|
||||
position: relative;
|
||||
padding: 0 !important;
|
||||
border: 1px solid var(--gray-300) !important;
|
||||
|
||||
.o-eurooffice-search-input {
|
||||
width: 100% !important;
|
||||
height: 24px;
|
||||
font-family: Arial;
|
||||
font-weight: 400;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
letter-spacing: 2%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
align-items: center;
|
||||
border: none;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: var(--gray-900);
|
||||
}
|
||||
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o-eurooffice-options-technical-name {
|
||||
.o-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
input {
|
||||
margin: 0 !important;
|
||||
border: 1px solid var(--gray-300) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o-eurooffice-fields-list {
|
||||
height: calc(100% - 248px);
|
||||
margin: 0 16px 16px 16px;
|
||||
padding-right: 4px;
|
||||
overflow-y: scroll;
|
||||
user-select: none;
|
||||
scrollbar-width: thin;
|
||||
|
||||
h3 {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.o-eurooffice-list-field {
|
||||
cursor: pointer;
|
||||
|
||||
.o-eurooffice-field {
|
||||
display: flex;
|
||||
position: relative;
|
||||
min-height: 22px;
|
||||
|
||||
.o-eurooffice-field-caret {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(25%, -50%);
|
||||
font-size: 0.875em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.o-eurooffice-field-title {
|
||||
padding-left: 12px;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
&:hover:not(.o-eurooffice-field_expanded) {
|
||||
background-color: var(--gray-200) !important;
|
||||
}
|
||||
|
||||
&.o-eurooffice-field_expanded {
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.o-eurooffice-list-field_expanded {
|
||||
/*border-bottom: 1px solid grey;*/
|
||||
}
|
||||
|
||||
.o-eurooffice-template-separator {
|
||||
margin: 2px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.o-eurooffice-list-field_children {
|
||||
padding-left: 18px !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.oe_eurooffice_kanban_bottom_right {
|
||||
.o_m2o_avatar {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
.o-eurooffice-templates-tree {
|
||||
.o-eurooffice-tree-list {
|
||||
.o-eurooffice-list-item {
|
||||
.o-eurooffice-item-body {
|
||||
.o-eurooffice-body-template {
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 6px;
|
||||
|
||||
.o-eurooffice-template-checkbox {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
/** @odoo-module **/
|
||||
import { EuroofficePreview } from "@eurooffice_odoo/views/preview/eurooffice_preview"
|
||||
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog"
|
||||
import { Dialog } from "@web/core/dialog/dialog"
|
||||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook"
|
||||
import { download } from "@web/core/network/download"
|
||||
import { Pager } from "@web/core/pager/pager"
|
||||
import { useService } from "@web/core/utils/hooks"
|
||||
import { SearchModel } from "@web/search/search_model"
|
||||
import { getDefaultConfig } from "@web/views/view"
|
||||
import { DropPrevious } from "web.concurrency"
|
||||
|
||||
const { Component, useState, useSubEnv, useChildSubEnv, onWillStart } = owl
|
||||
|
||||
export class TemplateDialog extends Component {
|
||||
setup() {
|
||||
this.orm = useService("orm")
|
||||
this.rpc = useService("rpc")
|
||||
this.viewService = useService("view")
|
||||
this.notificationService = useService("notification")
|
||||
this.dialog = useService("dialog")
|
||||
|
||||
this.data = this.env.dialogData
|
||||
useHotkey("escape", () => this.data.close())
|
||||
|
||||
this.dialogTitle = this.env._t("Print from template")
|
||||
this.limit = 8
|
||||
this.state = useState({
|
||||
currentOffset: 0,
|
||||
isOpen: true,
|
||||
isProcessing: false,
|
||||
selectedTemplateId: null,
|
||||
templates: [],
|
||||
totalTemplates: 0,
|
||||
})
|
||||
|
||||
useSubEnv({ config: { ...getDefaultConfig() } })
|
||||
|
||||
this.model = new SearchModel(this.env, {
|
||||
orm: this.orm,
|
||||
user: useService("user"),
|
||||
view: useService("view"),
|
||||
})
|
||||
|
||||
useChildSubEnv({ searchModel: this.model })
|
||||
|
||||
this.dp = new DropPrevious()
|
||||
|
||||
onWillStart(async () => {
|
||||
const { resModel } = this.props
|
||||
const views = await this.viewService.loadViews({
|
||||
context: this.props.context,
|
||||
resModel: "eurooffice.odoo.templates",
|
||||
views: [[false, "search"]],
|
||||
})
|
||||
await this.model.load({
|
||||
context: this.props.context,
|
||||
domain: [["template_model_model", "=", resModel]],
|
||||
orderBy: "id",
|
||||
resModel: "eurooffice.odoo.templates",
|
||||
searchMenuTypes: [],
|
||||
searchViewArch: views.views.search.arch,
|
||||
searchViewFields: views.fields,
|
||||
searchViewId: views.views.search.id,
|
||||
})
|
||||
await this.fetchTemplates()
|
||||
})
|
||||
}
|
||||
|
||||
async createTemplate() {
|
||||
// TODO: create template from dialog
|
||||
}
|
||||
|
||||
async fetchTemplates(offset = 0) {
|
||||
const { domain, context } = this.model
|
||||
const { records, length } = await this.dp.add(
|
||||
this.rpc("/web/dataset/search_read", {
|
||||
context,
|
||||
domain,
|
||||
fields: ["display_name", "name", "create_date", "create_uid", "attachment_id", "mimetype"],
|
||||
limit: this.limit,
|
||||
model: "eurooffice.odoo.templates",
|
||||
offset,
|
||||
sort: "id",
|
||||
}),
|
||||
)
|
||||
if (!length) {
|
||||
this.dialog.add(AlertDialog, {
|
||||
body: this.env._t(
|
||||
// eslint-disable-next-line @stylistic/max-len
|
||||
"You don't have any templates yet. Please go to the Euro-Office Templates app to create a new template or ask your admin to create it.",
|
||||
),
|
||||
title: this.dialogTitle,
|
||||
})
|
||||
return this.data.close()
|
||||
}
|
||||
this.state.templates = records
|
||||
this.state.totalTemplates = length
|
||||
}
|
||||
|
||||
async fillTemplate() {
|
||||
if (this.state.isProcessing) {
|
||||
return
|
||||
}
|
||||
this.state.isProcessing = true
|
||||
|
||||
const templateId = this.state.selectedTemplateId
|
||||
const { resId } = this.props
|
||||
|
||||
this.env.services.ui.block()
|
||||
try {
|
||||
await download({
|
||||
data: {
|
||||
record_ids: resId,
|
||||
template_id: templateId,
|
||||
},
|
||||
url: "/eurooffice/template/fill",
|
||||
})
|
||||
} finally {
|
||||
this.env.services.ui.unblock()
|
||||
}
|
||||
this.env.services.ui.unblock()
|
||||
this.data.close()
|
||||
}
|
||||
|
||||
selectTemplate(templateId) {
|
||||
this.state.selectedTemplateId = templateId
|
||||
}
|
||||
|
||||
isSelected(templateId) {
|
||||
return this.state.selectedTemplateId === templateId
|
||||
}
|
||||
|
||||
onPagerChange({ offset }) {
|
||||
this.state.currentOffset = offset
|
||||
this.state.selectedTemplateId = null
|
||||
return this.fetchTemplates(this.state.currentOffset)
|
||||
}
|
||||
|
||||
isButtonDisabled() {
|
||||
return this.state.isProcessing || this.state.selectedTemplateId === null
|
||||
}
|
||||
|
||||
previewTemplate() {
|
||||
const t = this.state.templates.find((item) => item.id === this.state.selectedTemplateId)
|
||||
const url = `/eurooffice/file/content/${t.attachment_id[0]}`
|
||||
|
||||
this.env.services.dialog.add(
|
||||
EuroofficePreview,
|
||||
{
|
||||
close: () => {
|
||||
this.env.services.dialog.close()
|
||||
},
|
||||
title: t.display_name + ".pdf",
|
||||
url: url,
|
||||
},
|
||||
{
|
||||
onClose: () => {
|
||||
return
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TemplateDialog.template = "eurooffice_odoo_templates.TemplateDialog"
|
||||
TemplateDialog.components = {
|
||||
Dialog,
|
||||
Pager,
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="eurooffice_odoo_templates.TemplateDialog" owl="1">
|
||||
<div>
|
||||
<Dialog
|
||||
t-if="state.isOpen"
|
||||
title="dialogTitle"
|
||||
contentClass="'o-spreadsheet-templates-dialog'"
|
||||
footer="true"
|
||||
modalRef="modalRef"
|
||||
>
|
||||
<div class="o-pager">
|
||||
<Pager
|
||||
t-if="state.totalTemplates"
|
||||
total="state.totalTemplates"
|
||||
offset="state.currentOffset"
|
||||
limit="limit"
|
||||
isEditable="false"
|
||||
onUpdate.bind="onPagerChange"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="o_kanban_renderer o_renderer d-flex o_kanban_ungrouped align-content-start flex-wrap justify-content-start"
|
||||
>
|
||||
<t t-foreach="state.templates" t-as="template" t-key="template.id">
|
||||
<div
|
||||
class="o_kanban_record d-flex flex-grow-1 flex-md-shrink-1 flex-shrink-0"
|
||||
tabindex="0"
|
||||
t-att-class="isSelected(template.id) ? 'o_eurooffice_kanban_record_selected': ''"
|
||||
t-on-focus="() => this.selectTemplate(template.id)"
|
||||
t-on-dblclick="fillTemplate"
|
||||
>
|
||||
<div class="oe_kanban_global_area oe_kanban_global_click o_kanban_attachment">
|
||||
<div class="o_kanban_image">
|
||||
<div class="o_kanban_image_wrapper">
|
||||
<div class="o_image o_image_thumbnail" t-att-data-mimetype="template.mimetype" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="o_kanban_details">
|
||||
<div class="o_kanban_details_wrapper">
|
||||
<div class="o_kanban_record_title" t-att-title="template.name">
|
||||
<span class="o_text_overflow" t-esc="template.name" />
|
||||
</div>
|
||||
|
||||
<div class="o_kanban_record_body">
|
||||
<samp class="text-muted" />
|
||||
</div>
|
||||
|
||||
<div class="o_kanban_record_bottom">
|
||||
<time class="oe_kanban_bottom_left">
|
||||
<div name="create_date" class="o_field_widget o_readonly_modifier o_field_date">
|
||||
<span t-esc="template.create_date" />
|
||||
</div>
|
||||
</time>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<div
|
||||
name="create_uid"
|
||||
class="o_field_widget o_readonly_modifier o_field_many2one_avatar_user o_field_many2one_avatar"
|
||||
>
|
||||
<div class="d-flex" t-att-data-tooltip="template.create_uid[1]">
|
||||
<span class="o_m2o_avatar">
|
||||
<img t-att-src="'/web/image/res.users/' + template.create_uid[0] + '/avatar_128'" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<t t-set-slot="footer">
|
||||
<button class="btn btn-primary o-template-fill" t-att-disabled="isButtonDisabled()" t-on-click="fillTemplate">
|
||||
<t>Print</t>
|
||||
</button>
|
||||
<button class="btn btn-secondary" t-att-disabled="isButtonDisabled()" t-on-click="previewTemplate">
|
||||
<t>Preview</t>
|
||||
</button>
|
||||
<button class="btn btn-secondary" t-on-click="data.close">
|
||||
<t>Cancel</t>
|
||||
</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry"
|
||||
import { useService } from "@web/core/utils/hooks"
|
||||
import { _t } from "web.core"
|
||||
import { ExportData } from "./eurooffice_editor_export_data"
|
||||
|
||||
const { Component, useState, onMounted, onWillUnmount } = owl
|
||||
|
||||
class TemplateEditor extends Component {
|
||||
setup() {
|
||||
super.setup()
|
||||
this.orm = useService("orm")
|
||||
this.rpc = useService("rpc")
|
||||
this.ExportData = ExportData
|
||||
this.notificationService = useService("notification")
|
||||
this.cookies = useService("cookie")
|
||||
this.router = useService("router")
|
||||
|
||||
this.state = useState({ resModel: "" })
|
||||
|
||||
this.config = null
|
||||
this.docApiJS = null
|
||||
this.documentReady = false
|
||||
this.hasLicense = false
|
||||
this.script = null
|
||||
this.unchangedModels = {}
|
||||
|
||||
this.env.bus.on("eurooffice-template-create-form", this, (field) => this.createForm(field))
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const attachment_id = this.props.action.params.attachment_id
|
||||
const template_model_model = this.props.action.params.template_model_model
|
||||
const id = this.props.action.params.id
|
||||
this.router.pushState({
|
||||
attachment_id: this.props.action.params.attachment_id,
|
||||
id: this.props.action.params.id,
|
||||
template_model_model: this.props.action.params.template_model_model,
|
||||
})
|
||||
|
||||
await this.orm.call("eurooffice.odoo.templates", "update_relationship", [id, template_model_model])
|
||||
|
||||
const response = await this.rpc("/eurooffice/template/editor", { attachment_id: attachment_id })
|
||||
const config = JSON.parse(response.editorConfig)
|
||||
config.events = {
|
||||
onDocumentReady: () => {
|
||||
if (window.docEditor && "createConnector" in window.docEditor) {
|
||||
window.connector = docEditor.createConnector()
|
||||
window.connector.executeMethod("GetVersion", [], () => {
|
||||
this.hasLicense = true
|
||||
})
|
||||
}
|
||||
// Render fields
|
||||
this.state.resModel = template_model_model
|
||||
this.documentReady = true
|
||||
},
|
||||
}
|
||||
const theme = this.cookies.current.color_scheme
|
||||
config.editorConfig.customization = {
|
||||
...config.editorConfig.customization,
|
||||
uiTheme: theme ? `default-${theme}` : "default-light",
|
||||
}
|
||||
this.config = config
|
||||
|
||||
this.docApiJS = response.docApiJS
|
||||
if (!window.DocsAPI) {
|
||||
await this.loadDocsAPI(this.docApiJS)
|
||||
}
|
||||
if (window.DocsAPI) {
|
||||
window.docEditor = new DocsAPI.DocEditor("doceditor", this.config)
|
||||
} else {
|
||||
throw new Error("window.DocsAPI is null")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("onMounted TemplateEditor error:", error)
|
||||
document.getElementById("error").classList.remove("d-none")
|
||||
}
|
||||
})
|
||||
|
||||
onWillUnmount(() => {
|
||||
if (window.connector) {
|
||||
window.connector.disconnect()
|
||||
delete window.connector
|
||||
}
|
||||
if (window.docEditor) {
|
||||
window.docEditor.destroyEditor()
|
||||
delete window.docEditor
|
||||
}
|
||||
if (this.script && this.script.parentNode) {
|
||||
this.script.parentNode.removeChild(this.script)
|
||||
}
|
||||
if (window.DocsAPI) {
|
||||
delete window.DocsAPI
|
||||
}
|
||||
this.env.bus.off("eurooffice-template-create-form", this)
|
||||
})
|
||||
}
|
||||
|
||||
async loadDocsAPI(DocsAPI) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script")
|
||||
script.src = DocsAPI
|
||||
script.onload = resolve
|
||||
script.onerror = reject
|
||||
document.body.appendChild(script)
|
||||
this.script = script
|
||||
})
|
||||
}
|
||||
|
||||
createForm(field) {
|
||||
if (this.documentReady) {
|
||||
if (!this.hasLicense) {
|
||||
this.notificationService.add(_t("Couldn't insert the field. Please check Automation API."), { type: "danger" })
|
||||
return
|
||||
}
|
||||
Asc.scope.data = field
|
||||
window.connector.callCommand(() => {
|
||||
var oDocument = Api.GetDocument()
|
||||
var oForm = null
|
||||
if (
|
||||
[
|
||||
"char",
|
||||
"text",
|
||||
"selection",
|
||||
"integer",
|
||||
"float",
|
||||
"monetary",
|
||||
"date",
|
||||
"datetime",
|
||||
"many2one",
|
||||
"one2many",
|
||||
"many2many",
|
||||
].includes(Asc.scope.data.field_type)
|
||||
) {
|
||||
oForm = Api.CreateTextForm({
|
||||
key: Asc.scope.data.id.replaceAll("/", " "),
|
||||
placeholder: Asc.scope.data.formattedString,
|
||||
tip: Asc.scope.data.formattedString,
|
||||
})
|
||||
}
|
||||
if (Asc.scope.data.field_type === "boolean") {
|
||||
oForm = Api.CreateCheckBoxForm({
|
||||
key: Asc.scope.data.id.replaceAll("/", " "),
|
||||
tip: Asc.scope.data.formattedString,
|
||||
})
|
||||
}
|
||||
if (Asc.scope.data.field_type === "binary") {
|
||||
oForm = Api.CreatePictureForm({
|
||||
key: Asc.scope.data.id.replaceAll("/", " "),
|
||||
tip: Asc.scope.data.formattedString,
|
||||
})
|
||||
}
|
||||
var oParagraph = Api.CreateParagraph()
|
||||
oParagraph.AddElement(oForm)
|
||||
oDocument.InsertContent([oParagraph], true, { KeepTextOnly: true })
|
||||
})
|
||||
|
||||
window.docEditor.grabFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateEditor.components = {
|
||||
...Component.components,
|
||||
ExportData,
|
||||
}
|
||||
TemplateEditor.template = "eurooffice_odoo_templates.TemplateEditor"
|
||||
|
||||
registry.category("actions").add("eurooffice_template_editor", TemplateEditor)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="eurooffice_odoo_templates.TemplateEditor" owl="1">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title> Euro-Office </title>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="o-eurooffice-template">
|
||||
<div class="o-eurooffice-template-fields border-end">
|
||||
<div class="o-eurooffice-fields-title">
|
||||
<span> Fields </span>
|
||||
</div>
|
||||
<hr class="o-eurooffice-template-separator" />
|
||||
<t t-if="state.resModel">
|
||||
<ExportData resModel="state.resModel" />
|
||||
</t>
|
||||
</div>
|
||||
<div class="o-eurooffice-template-editor">
|
||||
<div id="doceditor" />
|
||||
<div id="error" class="w-25 m-auto my-5 py-5 d-flex flex-column d-none">
|
||||
<img src="/eurooffice_odoo_templates/static/svg/eurooffice_logo.svg" alt="Euro-Office logo" />
|
||||
<div class="my-3 alert alert-danger">
|
||||
<p> Euro-Office cannot be reached. Please contact admin. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Component, useRef, useState, onWillStart } from "@odoo/owl"
|
||||
import { CheckBox } from "@web/core/checkbox/checkbox"
|
||||
import { unique } from "@web/core/utils/arrays"
|
||||
import { useService } from "@web/core/utils/hooks"
|
||||
import { fuzzyLookup } from "@web/core/utils/search"
|
||||
|
||||
class ExportDataItem extends Component {
|
||||
setup() {
|
||||
this.state = useState({ subfields: [] })
|
||||
onWillStart(() => {
|
||||
if (this.props.isExpanded) {
|
||||
return this.toggleItem(this.props.field, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async toggleItem(field, isUserToggle) {
|
||||
const id = field.id
|
||||
if (this.props.isFieldExpandable(id)) {
|
||||
if (this.state.subfields.length) {
|
||||
this.state.subfields = []
|
||||
} else {
|
||||
const subfields = await this.props.loadFields(id, !isUserToggle)
|
||||
if (subfields) {
|
||||
this.state.subfields = isUserToggle ? subfields : this.props.filterSubfields(subfields)
|
||||
} else {
|
||||
this.state.subfields = []
|
||||
}
|
||||
}
|
||||
} else if (isUserToggle) {
|
||||
this.env.bus.trigger("eurooffice-template-create-form", field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExportDataItem.template = "eurooffice_odoo_templates.ExportDataItem"
|
||||
ExportDataItem.components = { ExportDataItem }
|
||||
ExportDataItem.props = {
|
||||
field: {
|
||||
optional: true,
|
||||
type: Object,
|
||||
},
|
||||
filterSubfields: Function,
|
||||
isExpanded: Boolean,
|
||||
isFieldExpandable: Function,
|
||||
isTechnicalName: Boolean,
|
||||
loadFields: Function,
|
||||
}
|
||||
|
||||
export class ExportData extends Component {
|
||||
setup() {
|
||||
this.dialog = useService("dialog")
|
||||
this.notification = useService("notification")
|
||||
this.orm = useService("orm")
|
||||
this.rpc = useService("rpc")
|
||||
this.searchRef = useRef("search")
|
||||
|
||||
this.knownFields = {}
|
||||
this.expandedFields = {}
|
||||
|
||||
this.state = useState({
|
||||
exportList: [],
|
||||
isTechnicalName: false,
|
||||
search: [],
|
||||
})
|
||||
|
||||
onWillStart(async () => {
|
||||
await this.fetchFields()
|
||||
})
|
||||
}
|
||||
|
||||
get fieldsAvailable() {
|
||||
if (this.searchRef.el && this.searchRef.el.value) {
|
||||
return this.state.search.length && Object.values(this.state.search)
|
||||
}
|
||||
return Object.values(this.knownFields)
|
||||
}
|
||||
|
||||
get rootFields() {
|
||||
if (this.searchRef.el && this.searchRef.el.value) {
|
||||
const rootFromSearchResults = this.fieldsAvailable.map((f) => {
|
||||
if (f.parent) {
|
||||
const parentEl = this.knownFields[f.parent.id]
|
||||
return this.knownFields[parentEl.parent ? parentEl.parent.id : parentEl.id]
|
||||
}
|
||||
return this.knownFields[f.id]
|
||||
})
|
||||
return unique(rootFromSearchResults)
|
||||
}
|
||||
|
||||
return this.fieldsAvailable.filter(({ parent }) => !parent)
|
||||
}
|
||||
|
||||
filterSubfields(subfields) {
|
||||
let subfieldsFromSearchResults = []
|
||||
let searchResults = null
|
||||
if (this.searchRef.el && this.searchRef.el.value) {
|
||||
searchResults = this.lookup(this.searchRef.el.value)
|
||||
}
|
||||
const fieldsAvailable = Object.values(searchResults || this.knownFields)
|
||||
if (this.searchRef.el && this.searchRef.el.value) {
|
||||
subfieldsFromSearchResults = fieldsAvailable
|
||||
.filter((f) => f.parent && this.knownFields[f.parent.id].parent)
|
||||
.map((f) => f.parent)
|
||||
}
|
||||
const availableSubFields = unique([...fieldsAvailable, ...subfieldsFromSearchResults])
|
||||
return subfields.filter((a) => availableSubFields.some((b) => a.id === b.id))
|
||||
}
|
||||
|
||||
async fetchFields() {
|
||||
this.state.search = []
|
||||
this.knownFields = {}
|
||||
this.expandedFields = {}
|
||||
await this.loadFields()
|
||||
if (this.searchRef.el) {
|
||||
this.searchRef.el.value = ""
|
||||
}
|
||||
}
|
||||
|
||||
isFieldExpandable(id) {
|
||||
return this.knownFields[id].children && id.split("/").length < 4
|
||||
}
|
||||
|
||||
async loadFields(id, preventLoad = false) {
|
||||
let model = this.props.resModel
|
||||
let parentField = null
|
||||
let parentParams = {}
|
||||
if (id) {
|
||||
if (this.expandedFields[id]) {
|
||||
return this.expandedFields[id].fields
|
||||
}
|
||||
parentField = this.knownFields[id]
|
||||
model = parentField.params && parentField.params.model
|
||||
parentParams = {
|
||||
exclude: [parentField.relation_field],
|
||||
...parentField.params,
|
||||
parent_name: parentField.string,
|
||||
}
|
||||
}
|
||||
if (preventLoad) {
|
||||
return
|
||||
}
|
||||
const fields = await this.getExportedFields(model, parentParams)
|
||||
for (const field of fields) {
|
||||
field.formattedString = field.string.split("/").pop()
|
||||
field.formattedId = field.id.split("/").pop()
|
||||
field.parent = parentField
|
||||
if (!this.knownFields[field.id]) {
|
||||
this.knownFields[field.id] = field
|
||||
}
|
||||
}
|
||||
if (id) {
|
||||
this.expandedFields[id] = { fields }
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
async getExportedFields(model, parentParams = {}) {
|
||||
const fields = await this.orm.call("eurooffice.odoo.templates", "get_fields_for_model", [
|
||||
model,
|
||||
parentParams.prefix || "",
|
||||
parentParams.parent_name || "",
|
||||
parentParams.exclude,
|
||||
])
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
onSearch(ev) {
|
||||
this.state.search = this.lookup(ev.target.value)
|
||||
}
|
||||
|
||||
onCleanSearch() {
|
||||
this.searchRef.el.value = ""
|
||||
this.state.search = []
|
||||
}
|
||||
|
||||
lookup(value) {
|
||||
let lookupResult = null
|
||||
if (this.state.isTechnicalName) {
|
||||
lookupResult = fuzzyLookup(value, Object.values(this.knownFields), (field) => field.id.replaceAll("/", " "))
|
||||
} else {
|
||||
lookupResult = fuzzyLookup(value, Object.values(this.knownFields), (field) => field.string.replaceAll("/", " "))
|
||||
}
|
||||
return lookupResult
|
||||
}
|
||||
|
||||
onToggleDisplayOption(value) {
|
||||
// This.onCleanSearch()
|
||||
this.state.isTechnicalName = value
|
||||
}
|
||||
}
|
||||
ExportData.components = {
|
||||
CheckBox,
|
||||
ExportDataItem,
|
||||
}
|
||||
ExportData.props = { resModel: String }
|
||||
ExportData.template = "eurooffice_odoo_templates.ExportData"
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="eurooffice_odoo_templates.ExportDataItem" owl="1">
|
||||
<div
|
||||
t-att-data-field_id="props.field.id"
|
||||
t-attf-class="o-eurooffice-list-field {{ state.subfields.length ? 'o-eurooffice-list-field_expanded mb-2' : '' }} {{ props.field.parent ? 'o-eurooffice-list-field_children' : ''}}"
|
||||
role="treeitem"
|
||||
t-on-click.stop="() => this.toggleItem(props.field, true)"
|
||||
t-attf-data-tooltip="{{ props.isTechnicalName ? props.field.string : props.field.id }}"
|
||||
>
|
||||
<div
|
||||
t-attf-class="o-eurooffice-field list-group-item-action {{ state.subfields.length ? 'o-eurooffice-field_expanded list-group-item active' : '' }}"
|
||||
>
|
||||
<span
|
||||
t-if="props.isFieldExpandable(props.field.id)"
|
||||
t-attf-class="o-eurooffice-field-caret fa {{ state.subfields.length ? 'fa-caret-down' : 'fa-caret-right' }}"
|
||||
role="img"
|
||||
aria-label="Show sub-fields"
|
||||
title="Show sub-fields"
|
||||
/>
|
||||
<span t-if="props.isTechnicalName" class="o-eurooffice-field-title" t-esc="props.field.formattedId" />
|
||||
<span t-else="" class="o-eurooffice-field-title" t-esc="props.field.formattedString" />
|
||||
<!--<span
|
||||
t-if="props.field.field_type === 'binary'"
|
||||
title="Image"
|
||||
t-attf-class="o-eurooffice-field-type fa fa-picture-o float-end m-1"
|
||||
/>-->
|
||||
</div>
|
||||
<t t-foreach="state.subfields" t-as="field" t-key="field.id">
|
||||
<ExportDataItem t-props="props" field="field" />
|
||||
<t t-if="field_index === state.subfields.length-1">
|
||||
<hr class="o-eurooffice-template-separator" />
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="eurooffice_odoo_templates.ExportData" owl="1">
|
||||
<div class="o-eurooffice-fields-options">
|
||||
<div class="o-eurooffice-options-help">
|
||||
<span> Choose the field below to paste selected parameter to the cursor </span>
|
||||
</div>
|
||||
<div class="o-eurooffice-options-title">
|
||||
<span>Module/ section</span>
|
||||
</div>
|
||||
<div class="o-eurooffice-options-search">
|
||||
<div class="o-eurooffice-search-field o_searchview">
|
||||
<input
|
||||
t-ref="search"
|
||||
class="o-eurooffice-search-input o_searchview_input form-control"
|
||||
id="o-export-search-filter"
|
||||
t-attf-placeholder="{{ state.isTechnicalName ? 'Search technical name' : 'Search field' }}"
|
||||
t-on-input="onSearch"
|
||||
/>
|
||||
<button t-if="this.searchRef.el and this.searchRef.el.value" t-on-click="onCleanSearch">
|
||||
<svg width="12" height="11" viewBox="0 0 12 11" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M0.810547 0.707106L1.4718 1.36836L2.7943 2.69087L5.29585 5.19244L3.00452 7.48377L1.57703 8.91125L0.863283 9.625L1.57039 10.3321L2.28413 9.61836L3.71162 8.19087L6.00295 5.89954L8.2943 8.19089L9.7218 9.61839L10.4355 10.3321L11.1427 9.62503L10.4289 8.91128L9.0014 7.48378L6.71006 5.19244L9.21162 2.69087L10.5341 1.36836L11.1954 0.707108L10.4883 0L9.82703 0.661255L8.50452 1.98377L6.00295 4.48533L3.50141 1.98377L2.17891 0.661257L1.51766 0L0.810547 0.707106Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button t-else="">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10 5.5C10 7.98528 7.98528 10 5.5 10C3.01472 10 1 7.98528 1 5.5C1 3.01472 3.01472 1 5.5 1C7.98528 1 10 3.01472 10 5.5ZM9.01953 9.72663C8.06578 10.5217 6.83875 11 5.5 11C2.46243 11 0 8.53757 0 5.5C0 2.46243 2.46243 0 5.5 0C8.53757 0 11 2.46243 11 5.5C11 6.83875 10.5217 8.06578 9.72663 9.01953L13.8536 13.1465L13.1465 13.8536L9.01953 9.72663Z"
|
||||
fill="black"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o-eurooffice-options-technical-name">
|
||||
<CheckBox id="'o-display-option'" value="state.isTechnicalName" onChange.bind="onToggleDisplayOption">
|
||||
Use a technical name
|
||||
</CheckBox>
|
||||
</div>
|
||||
<hr class="o-eurooffice-template-separator" />
|
||||
</div>
|
||||
<div class="o-eurooffice-fields-list">
|
||||
<t t-if="fieldsAvailable">
|
||||
<t t-foreach="rootFields" t-as="field" t-key="field.id + '_' + state.search.length + '_' + state.isCompatible">
|
||||
<ExportDataItem
|
||||
field="field"
|
||||
filterSubfields.bind="filterSubfields"
|
||||
isExpanded="state.search.length > 0"
|
||||
isFieldExpandable.bind="isFieldExpandable"
|
||||
isTechnicalName="state.isTechnicalName"
|
||||
loadFields.bind="loadFields"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
<h3 t-else="" class="text-center text-muted mt-5 o_no_match">No match found.</h3>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/** @odoo-module **/
|
||||
import { patch } from "@web/core/utils/patch"
|
||||
import { FormController } from "@web/views/form/form_controller"
|
||||
import { TemplateDialog } from "../dialog/eurooffice_dialog"
|
||||
|
||||
patch(FormController.prototype, "FormController.ActionButton", {
|
||||
/**
|
||||
* @override
|
||||
**/
|
||||
getActionMenuItems() {
|
||||
const menuItems = this._super()
|
||||
menuItems.other.push({
|
||||
callback: () => {
|
||||
this.env.services.dialog.add(TemplateDialog, {
|
||||
resId: this.model.root.resId,
|
||||
resModel: this.props.resModel,
|
||||
})
|
||||
},
|
||||
description: this.env._t("Print with Euro-Office"),
|
||||
skipSave: true,
|
||||
})
|
||||
return menuItems
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/** @odoo-module **/
|
||||
import { Dialog } from "@web/core/dialog/dialog"
|
||||
import { _t } from "@web/core/l10n/translation"
|
||||
|
||||
const { Component } = owl
|
||||
|
||||
export class HelpDialog extends Component {
|
||||
setup() {
|
||||
this.title = _t("Help")
|
||||
console.log(this)
|
||||
}
|
||||
}
|
||||
|
||||
HelpDialog.template = "eurooffice_odoo_templates.HelpDialog"
|
||||
HelpDialog.components = { Dialog }
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="eurooffice_odoo_templates.HelpDialog" owl="1">
|
||||
<div>
|
||||
<Dialog title="title" footer="true" size="'md'">
|
||||
<span class="fw-bolder">
|
||||
Euro-Office Templates module
|
||||
</span>
|
||||
<p>The Euro-Office Templates module allows you to create, manage, and use fillable PDF <span
|
||||
class="fw-bolder"
|
||||
>templates </span> for any installed Odoo module (e.g., Sales, Invoicing, CRM). These templates can then be filled in automatically with real-time data from your Odoo system — no manual input needed.</p>
|
||||
<p
|
||||
>On this page, you can create form templates for any Odoo module, such as Invoice, Receipt, Employee Leave Form, etc. Just click <span
|
||||
class="fw-bolder"
|
||||
>Create or Upload</span>, select the desired Odoo module, and create or upload a PDF template with fillable fields.</p>
|
||||
<br />
|
||||
<span class="fw-bolder">To fill in a template with Odoo data:</span>
|
||||
<ol>
|
||||
<li>Go to the relevant Odoo module (e.g., Sales, Invoices).</li>
|
||||
<li>Open a record.</li>
|
||||
<li>Click on the Action button.</li>
|
||||
<li>Select Print with Euro-Office.</li>
|
||||
<li>Choose the required template to fill in with Odoo data.</li>
|
||||
<li>Press the Print button.</li>
|
||||
</ol>
|
||||
<p
|
||||
>This will generate a PDF document with your template, automatically filled with the corresponding record's data.</p>
|
||||
<t t-set-slot="footer">
|
||||
<button class="btn btn-primary" t-on-click="props.close">
|
||||
<t>Ok</t>
|
||||
</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/** @odoo-module */
|
||||
import { FormGallery } from "@eurooffice_odoo/views/form_gallery/form_gallery"
|
||||
import { useService } from "@web/core/utils/hooks"
|
||||
import { KanbanController } from "@web/views/kanban/kanban_controller"
|
||||
import { HelpDialog } from "./eurooffice_dialog_help"
|
||||
|
||||
export class EuroofficeKanbanController extends KanbanController {
|
||||
setup() {
|
||||
super.setup()
|
||||
this.action = useService("action")
|
||||
this.orm = useService("orm")
|
||||
this.notificationService = useService("notification")
|
||||
this.dialog = useService("dialog")
|
||||
this.openedFormGallery = false
|
||||
}
|
||||
|
||||
async openFormGallery() {
|
||||
const download = (form) => {
|
||||
if (form) {
|
||||
this.action.doAction({
|
||||
context: {
|
||||
default_hide_file_field: true,
|
||||
default_name: form.attributes.name_form,
|
||||
url: form.attributes.file_oform.data[0].attributes.url,
|
||||
},
|
||||
res_model: "eurooffice.odoo.templates",
|
||||
target: "current",
|
||||
type: "ir.actions.act_window",
|
||||
view_mode: "form",
|
||||
views: [[false, "form"]],
|
||||
})
|
||||
}
|
||||
}
|
||||
this.dialog.add(FormGallery, {
|
||||
onDownload: download,
|
||||
showType: false,
|
||||
})
|
||||
}
|
||||
|
||||
async help() {
|
||||
this.dialog.add(HelpDialog, {})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates>
|
||||
<t
|
||||
t-name="eurooffice_odoo_templates.EuroofficeKanbanController.Buttons"
|
||||
t-inherit="web.KanbanView.Buttons"
|
||||
t-inherit-mode="primary"
|
||||
owl="1"
|
||||
>
|
||||
<xpath expr="//button[hasclass('o-kanban-button-new')]" position="replace">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary o-kanban-button-new"
|
||||
accesskey="c"
|
||||
t-on-click="() => this.createRecord(null)"
|
||||
data-bounce-button=""
|
||||
groups="eurooffice_odoo_templates.group_manager"
|
||||
>
|
||||
Create or Upload
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_cp_buttons')]" position="inside">
|
||||
<button class="btn btn-secondary ms-1" t-on-click="() => this.openFormGallery()" style="text-wrap: nowrap;">
|
||||
Open Document templates
|
||||
</button>
|
||||
<button class="btn btn-secondary ms-1" t-on-click="() => this.help()"> Help </button>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/** @odoo-module **/
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"
|
||||
import { useService } from "@web/core/utils/hooks"
|
||||
import { CANCEL_GLOBAL_CLICK, KanbanRecord } from "@web/views/kanban/kanban_record"
|
||||
|
||||
export class EuroofficeKanbanRecord extends KanbanRecord {
|
||||
setup() {
|
||||
super.setup()
|
||||
this.orm = useService("orm")
|
||||
this.actionService = useService("action")
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
triggerAction(params) {
|
||||
const env = this.env
|
||||
const { group, list, openRecord, record } = this.props
|
||||
const { type } = params
|
||||
switch (type) {
|
||||
case "edit": {
|
||||
return openRecord(record, "edit")
|
||||
}
|
||||
case "delete": {
|
||||
const listOrGroup = group || list
|
||||
if (listOrGroup.deleteRecords) {
|
||||
this.dialog.add(ConfirmationDialog, {
|
||||
body: env._t("Are you sure you want to delete this record?"),
|
||||
cancel: () => {
|
||||
return
|
||||
},
|
||||
confirm: async () => {
|
||||
await listOrGroup.deleteRecords([record])
|
||||
this.props.record.model.load()
|
||||
this.props.record.model.notify()
|
||||
return this.notification.add(env._t("Template removed"), {
|
||||
sticky: false,
|
||||
type: "info",
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
default: {
|
||||
return this.notification.add(env._t("Kanban: no action for type: ") + type, { type: "danger" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async onGlobalClick(ev) {
|
||||
if (ev.target.closest(CANCEL_GLOBAL_CLICK) && !ev.target.classList.contains("o_eurooffice_download")) {
|
||||
return
|
||||
}
|
||||
if (ev.target.classList.contains("o_eurooffice_download")) {
|
||||
window.location.href = `/eurooffice/template/download/${this.props.record.data.attachment_id[0]}`
|
||||
return
|
||||
}
|
||||
return this.editTemplate()
|
||||
}
|
||||
|
||||
async editTemplate() {
|
||||
const action = {
|
||||
params: {
|
||||
attachment_id: this.props.record.data.attachment_id[0],
|
||||
id: this.props.record.data.id,
|
||||
template_model_model: this.props.record.data.template_model_model,
|
||||
},
|
||||
tag: "eurooffice_template_editor",
|
||||
target: "current",
|
||||
type: "ir.actions.client",
|
||||
}
|
||||
return this.actionService.doAction(action, { props: this.props.record.data })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { KanbanRenderer } from "@web/views/kanban/kanban_renderer"
|
||||
import { EuroofficeKanbanRecord } from "./eurooffice_kanban_record"
|
||||
|
||||
export class EuroofficeKanbanRenderer extends KanbanRenderer {
|
||||
setup() {
|
||||
super.setup()
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
**/
|
||||
canQuickCreate() {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
**/
|
||||
canCreateGroup() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
EuroofficeKanbanRenderer.components = {
|
||||
...KanbanRenderer.components,
|
||||
KanbanRecord: EuroofficeKanbanRecord,
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/** @odoo-module */
|
||||
import { registry } from "@web/core/registry"
|
||||
import { kanbanView } from "@web/views/kanban/kanban_view"
|
||||
import { EuroofficeKanbanController } from "./eurooffice_kanban_controller"
|
||||
import { EuroofficeKanbanRenderer } from "./eurooffice_kanban_renderer"
|
||||
|
||||
export const euroofficeKanbanView = {
|
||||
...kanbanView,
|
||||
Controller: EuroofficeKanbanController,
|
||||
Renderer: EuroofficeKanbanRenderer,
|
||||
buttonTemplate: "eurooffice_odoo_templates.EuroofficeKanbanController.Buttons",
|
||||
}
|
||||
|
||||
registry.category("views").add("eurooffice_kanban", euroofficeKanbanView)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/** @odoo-module **/
|
||||
import { patch } from "@web/core/utils/patch"
|
||||
import { ListController } from "@web/views/list/list_controller"
|
||||
import { TemplateDialog } from "../dialog/eurooffice_dialog"
|
||||
|
||||
patch(ListController.prototype, "ListController.ActionButton", {
|
||||
/**
|
||||
* @override
|
||||
**/
|
||||
getActionMenuItems() {
|
||||
const menuItems = this._super()
|
||||
menuItems.other.push({
|
||||
callback: async () => {
|
||||
this.env.services.dialog.add(TemplateDialog, {
|
||||
resId: await this.model.root.getResIds(true),
|
||||
resModel: this.props.resModel,
|
||||
})
|
||||
},
|
||||
description: this.env._t("Print with Euro-Office"),
|
||||
skipSave: true,
|
||||
})
|
||||
return menuItems
|
||||
},
|
||||
})
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Component, useState, onWillStart } from "@odoo/owl"
|
||||
import { EuroofficePreview } from "@eurooffice_odoo/views/preview/eurooffice_preview"
|
||||
import { registry } from "@web/core/registry"
|
||||
import { useService } from "@web/core/utils/hooks"
|
||||
|
||||
export class TemplatesTree extends Component {
|
||||
setup() {
|
||||
this.orm = useService("orm")
|
||||
this.state = useState({
|
||||
loading: true,
|
||||
selected: [],
|
||||
structure: {},
|
||||
})
|
||||
|
||||
onWillStart(async () => {
|
||||
await this.loadTemplates()
|
||||
})
|
||||
}
|
||||
|
||||
async loadTemplates() {
|
||||
try {
|
||||
const data = await this.orm.call("eurooffice.odoo.demo.templates", "get_template_data")
|
||||
this.state.structure = data.structure || {}
|
||||
this.state.selected = data.selected || []
|
||||
} catch (error) {
|
||||
console.error("Failed to load templates:", error)
|
||||
} finally {
|
||||
this.state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
toggleTemplate(path, checked) {
|
||||
if (checked) {
|
||||
this.state.selected.push(path)
|
||||
} else {
|
||||
this.state.selected = this.state.selected.filter((p) => p !== path)
|
||||
}
|
||||
if (this.props.record) {
|
||||
this.props.record.update({ selected_templates: JSON.stringify(this.state.selected) })
|
||||
}
|
||||
}
|
||||
|
||||
async saveSelection() {
|
||||
const value = JSON.stringify({
|
||||
selected: this.state.selected,
|
||||
structure: this.state.structure,
|
||||
})
|
||||
|
||||
await this.orm.write("eurooffice.odoo.demo.templates", [this.props.record.resId], { selected_templates: value })
|
||||
|
||||
if (this.props.update) {
|
||||
this.props.update(value)
|
||||
}
|
||||
}
|
||||
|
||||
previewTemplate(path) {
|
||||
const url = `/eurooffice/template/template_content/${encodeURIComponent(path.replace("/", "_"))}`
|
||||
|
||||
this.env.services.dialog.add(
|
||||
EuroofficePreview,
|
||||
{
|
||||
close: () => {
|
||||
this.env.services.dialog.close()
|
||||
},
|
||||
title: path.split("/").pop(),
|
||||
url: url,
|
||||
},
|
||||
{
|
||||
onClose: () => {
|
||||
return
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TemplatesTree.template = "eurooffice_odoo_templates.TemplatesTree"
|
||||
|
||||
registry.category("fields").add("eurooffice_template_tree", TemplatesTree)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="eurooffice_odoo_templates.TemplatesTree" owl="1">
|
||||
<div class="o-eurooffice-templates-tree">
|
||||
<t t-if="state.loading">
|
||||
<div class="text-center p-4">
|
||||
<i class="fa fa-refresh fa-spin" />
|
||||
Loading templates...
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="o-eurooffice-tree-list">
|
||||
<t t-foreach="Object.values(state.structure)" t-as="category" t-key="category.model">
|
||||
<div class="o-eurooffice-list-item">
|
||||
<h3 class="o-eurooffice-item-header">
|
||||
<t t-esc="category.name" />
|
||||
</h3>
|
||||
<div class="o-eurooffice-item-body">
|
||||
<t t-foreach="category.files" t-as="file" t-key="file.path">
|
||||
<div class="o-eurooffice-body-template">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="o-eurooffice-template-checkbox form-check-input"
|
||||
t-att-checked="state.selected.includes(file.path)"
|
||||
t-on-change="(ev) => this.toggleTemplate(file.path, ev.target.checked)"
|
||||
/>
|
||||
<button class="btn btn-sm btn-link" t-on-click="() => this.previewTemplate(file.path)">
|
||||
<i class="fa fa-eye" />
|
||||
</button>
|
||||
<span t-esc="file.name" />
|
||||
</label>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<svg width="433" height="70" viewBox="0 0 433 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M100.633 19.9039C96.3343 23.3872 94.2545 28.7462 94.2545 35.847C94.2545 42.9475 96.3343 48.3063 100.633 51.9237C104.931 55.541 109.784 57.2828 115.469 57.2828C121.016 57.2828 126.007 55.541 130.167 51.9237C134.327 48.4404 136.407 43.0813 136.407 35.9808C136.407 28.7462 134.327 23.5212 130.167 19.9039C126.007 16.2866 121.154 14.5449 115.469 14.5449C109.784 14.5449 104.793 16.2866 100.633 19.9039ZM106.179 46.5649C104.377 44.2874 103.406 40.8038 103.406 35.847C103.406 31.0237 104.377 27.5405 106.179 25.3969C108.12 23.1193 110.2 21.6456 112.419 21.1097L112.419 21.1097C112.973 20.9757 113.528 20.8418 113.944 20.8418C114.36 20.7078 114.776 20.7078 115.33 20.7078H115.331C115.608 20.7078 115.85 20.7413 116.093 20.7748C116.336 20.8083 116.578 20.8418 116.856 20.8418C117.272 20.8418 117.827 20.9757 118.381 21.1097C120.6 21.6456 122.68 23.1193 124.482 25.3969C126.285 27.6745 127.255 31.2919 127.255 35.9808C127.255 40.67 126.285 44.2874 124.482 46.5649C122.68 48.8425 120.6 50.3161 118.381 50.8522C118.286 50.8752 118.194 50.8983 118.105 50.9207C117.679 51.0282 117.315 51.1201 116.856 51.1201C116.301 51.2539 115.885 51.2539 115.331 51.2539H115.331C115.123 51.2539 114.88 51.2204 114.637 51.187C114.395 51.1535 114.152 51.1201 113.944 51.1201C113.485 51.1201 113.121 51.0282 112.695 50.9208C112.606 50.8983 112.514 50.8752 112.419 50.8522C110.2 50.3161 108.12 48.8425 106.179 46.5649Z" fill="#333333"/>
|
||||
<path d="M140.982 14.9468H152.352L167.327 41.3398L169.546 46.9668H169.685L169.546 39.598V14.9468H178.281V56.7469H166.912L151.936 29.416L149.718 24.7269H149.579L149.718 32.0954V56.7469H140.982V14.9468Z" fill="#333333"/>
|
||||
<path d="M185.908 14.9468H194.643V49.646H211.837V56.7469H185.908V14.9468Z" fill="#333333"/>
|
||||
<path d="M206.43 14.9468H216.552L225.426 29.684L226.812 32.4974H227.09L228.476 29.684L237.489 14.9468H246.779L231.111 39.7321V56.7469H222.375V39.7321L206.43 14.9468Z" fill="#333333"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M253.434 19.9039C249.136 23.3872 247.056 28.7462 247.056 35.847C247.056 42.9475 249.275 48.3063 253.434 51.9237C257.733 55.541 262.586 57.2828 268.271 57.2828C273.817 57.2828 278.809 55.541 282.969 51.9237C287.128 48.4404 289.208 43.0813 289.208 35.9808C289.208 28.7462 287.128 23.5212 282.969 19.9039C278.809 16.2866 273.956 14.5449 268.271 14.5449C262.586 14.5449 257.594 16.2866 253.434 19.9039ZM258.981 46.5649C257.178 44.2874 256.208 40.8038 256.208 35.847C256.208 31.0237 257.178 27.5405 258.981 25.3969C260.922 23.1193 263.002 21.6456 265.22 21.1097C265.775 20.9757 266.33 20.8418 266.746 20.8418C267.162 20.7078 267.577 20.7078 268.132 20.7078H268.132C268.41 20.7078 268.652 20.7413 268.895 20.7748C269.137 20.8083 269.38 20.8418 269.657 20.8418C270.073 20.8418 270.627 20.9755 271.182 21.1094L271.183 21.1097C273.401 21.6456 275.481 23.1193 277.284 25.3969C279.086 27.6745 280.057 31.2919 280.057 35.9808C280.057 40.67 279.086 44.2874 277.284 46.5649C275.481 48.8425 273.401 50.3161 271.183 50.8522C271.087 50.8753 270.995 50.8984 270.906 50.9209C270.48 51.0283 270.116 51.1201 269.657 51.1201C269.103 51.2539 268.687 51.2539 268.132 51.2539H268.132C267.924 51.2539 267.682 51.2204 267.439 51.187C267.196 51.1535 266.954 51.1201 266.746 51.1201C266.331 51.1201 265.778 50.9867 265.225 50.8533L265.22 50.8522C262.863 50.3161 260.922 48.8425 258.981 46.5649Z" fill="#333333"/>
|
||||
<path d="M293.923 14.9468H318.049V22.0474H302.658V32.0954H317.355V39.196H302.658V56.7469H293.923V14.9468Z" fill="#333333"/>
|
||||
<path d="M323.181 14.9468H347.307V22.0474H331.915V32.0954H346.613V39.196H331.915V56.7469H323.181V14.9468Z" fill="#333333"/>
|
||||
<path d="M352.436 56.7469V14.9468H361.173V56.7469H352.436Z" fill="#333333"/>
|
||||
<path d="M400.624 15.8843V23.1189C399.099 22.583 397.573 22.1811 395.91 21.9131C394.247 21.6452 392.444 21.5112 390.501 21.5112C385.927 21.5112 382.461 22.851 379.964 25.6644C377.469 28.3439 376.221 31.8272 376.221 35.9803C376.221 39.9997 377.329 43.3491 379.687 46.0287C382.044 48.7079 385.372 50.1818 389.67 50.1818C391.195 50.1818 392.721 50.0477 394.524 49.9139C396.324 49.6457 398.127 49.244 400.07 48.44L400.624 55.5406C400.347 55.6747 399.93 55.8088 399.516 55.9426C398.961 56.0767 398.404 56.2105 397.713 56.3446C396.604 56.6125 395.216 56.7466 393.553 57.0145C391.89 57.1483 390.224 57.2824 388.421 57.2824C388.144 57.2824 387.867 57.2824 387.73 57.2824C387.453 57.2824 387.175 57.2824 387.035 57.2824C382.044 57.0145 377.469 55.1389 373.309 51.9233C369.149 48.5741 367.069 43.3491 367.069 36.3823C367.069 29.5497 369.149 24.1907 373.169 20.4394C377.189 16.6881 382.738 14.8125 389.533 14.8125C391.333 14.8125 392.998 14.8125 394.384 14.9464C395.91 15.0804 397.296 15.3484 398.821 15.6163C399.099 15.7503 399.516 15.7503 399.793 15.8843C399.93 15.7503 400.207 15.8843 400.624 15.8843Z" fill="#333333"/>
|
||||
<path d="M406.08 14.9468H432.009V21.5115H414.955V31.9616H430.346V38.5264H414.955V50.1821H432.009V56.7469H406.08V14.9468Z" fill="#333333"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.3143 69.1577L2.00187 55.01C-0.665498 53.7748 -0.665498 51.8661 2.00187 50.7431L12.9033 45.8027L33.1986 55.01C35.8658 56.245 40.1569 56.245 42.7081 55.01L63.0033 45.8027L73.9049 50.7431C76.5721 51.9784 76.5721 53.8871 73.9049 55.01L42.5921 69.1577C40.1569 70.2807 35.8658 70.2807 33.3143 69.1577Z" fill="#FF6F3D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.3143 51.0822L2.00187 36.9345C-0.665498 35.6993 -0.665498 33.7906 2.00187 32.6676L12.6713 27.8394L33.3143 37.1591C35.9818 38.3941 40.2726 38.3941 42.8241 37.1591L63.4674 27.8394L74.1366 32.6676C76.8042 33.9028 76.8042 35.8116 74.1366 36.9345L42.8241 51.0822C40.1569 52.3175 35.8658 52.3175 33.3143 51.0822Z" fill="#95C038"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.3131 33.4888L2.00052 19.341C-0.666841 18.1058 -0.666841 16.197 2.00052 15.0742L33.3131 0.926346C35.9803 -0.308782 40.2714 -0.308782 42.8229 0.926346L74.1354 15.0742C76.8027 16.3093 76.8027 18.2181 74.1354 19.341L42.8229 33.4888C40.1554 34.6117 35.8646 34.6117 33.3131 33.4888Z" fill="#5DC0E8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6 KiB |