mirror of
https://github.com/bringout/oca-ocb-technical.git
synced 2026-04-18 20:32:02 +02:00
Initial commit: Technical packages
This commit is contained in:
commit
3473fa71a0
873 changed files with 297766 additions and 0 deletions
BIN
odoo-bringout-oca-ocb-note/note/static/description/icon.png
Normal file
BIN
odoo-bringout-oca-ocb-note/note/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
|
|
@ -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="98.616%"><stop offset="0%" stop-color="#797C79"/><stop offset="100%" stop-color="#545554"/></linearGradient><path id="d" d="M56.342 31.863l-1.875 1.876a.489.489 0 0 1-.692 0l-4.517-4.516a.489.489 0 0 1 0-.692l1.876-1.876c.761-.76 1.998-.76 2.763 0l2.445 2.446a1.95 1.95 0 0 1 0 2.762zM15 47v-2h17v2H15zm0-4.915v-2h19v2H15zm0-9v-2h22v2H15zm0 5v-2h14v2H15zm0-9v-2h28v2H15zm32.647 1.057a.494.494 0 0 1 .696 0l4.516 4.516c.192.192.192.5 0 .692L42.174 46.034l-4.944.867a.978.978 0 0 1-1.13-1.131l.862-4.944 10.685-10.684zm-6.515 9.769a.567.567 0 0 0 .806 0l6.266-6.266a.567.567 0 0 0 0-.805.567.567 0 0 0-.805 0l-6.267 6.265a.567.567 0 0 0 0 .806zm-1.468 3.422V41.38h-1.477l-.46 2.624 1.265 1.265 2.625-.46v-1.476h-1.953z"/><path id="e" d="M56.342 29.863l-1.875 1.876a.489.489 0 0 1-.692 0l-4.517-4.516a.489.489 0 0 1 0-.692l1.876-1.876c.761-.76 1.998-.76 2.763 0l2.445 2.446a1.95 1.95 0 0 1 0 2.762zM15 45v-2h17v2H15zm0-4.915v-2h19v2H15zm0-9v-2h22v2H15zm0 5v-2h14v2H15zm0-9v-2h28v2H15zm32.647 1.057a.494.494 0 0 1 .696 0l4.516 4.516c.192.192.192.5 0 .692L42.174 44.034l-4.944.867a.978.978 0 0 1-1.13-1.131l.862-4.944 10.685-10.684zm-6.515 9.769a.567.567 0 0 0 .806 0l6.266-6.266a.567.567 0 0 0 0-.805.567.567 0 0 0-.805 0l-6.267 6.265a.567.567 0 0 0 0 .806zm-1.468 3.422V39.38h-1.477l-.46 2.624 1.265 1.265 2.625-.46v-1.476h-1.953z"/></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-4V41.668l15.07-16.471h27.883v1.843L31.949 38.148l1.998 1.89-4.983 4.239h1.513l21.088-19.832L56 30 25.947 68.643 4 69z" 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" xlink:href="#d"/><use fill="#FFF" fill-rule="nonzero" xlink:href="#e"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
|
@ -0,0 +1,26 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
|
||||
import { useUpdateToModel } from '@mail/component_hooks/use_update_to_model';
|
||||
import '@mail/components/activity_menu_view/activity_menu_view'; // ensure components are registered beforehand.
|
||||
import { getMessagingComponent } from "@mail/utils/messaging_component";
|
||||
|
||||
import { DatePicker } from '@web/core/datepicker/datepicker';
|
||||
import { patch } from 'web.utils';
|
||||
|
||||
const ActivityMenuView = getMessagingComponent('ActivityMenuView');
|
||||
|
||||
patch(ActivityMenuView.prototype, 'note', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
this._super();
|
||||
useRefToModel({ fieldName: 'noteInputRef', refName: 'noteInput', });
|
||||
useUpdateToModel({ methodName: 'onComponentUpdate' });
|
||||
},
|
||||
});
|
||||
|
||||
Object.assign(ActivityMenuView.components, {
|
||||
DatePicker,
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="mail.ActivityMenuView" t-inherit-mode="extension">
|
||||
<xpath expr="//*[@name='activityGroupLoop']" position="after">
|
||||
<div t-if="!activityMenuView.isAddingNote" class="o_note_show d-grid" t-on-click="activityMenuView.onClickAddNote">
|
||||
<a role="button" class="btn text-center">Add new note</a>
|
||||
</div>
|
||||
<div t-if="activityMenuView.isAddingNote" class="o_note o_ActivityMenuView_activityGroup">
|
||||
<div class="o_ActivityMenuView_activityGroupIconContainer">
|
||||
<img src="/note/static/description/icon.svg" alt="Channel"/>
|
||||
</div>
|
||||
<div class="o_ActivityMenuView_activityGroupInfo">
|
||||
<div class="o_ActivityMenuView_activityGroupTitle">
|
||||
<span class="o_ActivityMenuView_activityGroupName"><strong>Add a note</strong></span>
|
||||
<DatePicker
|
||||
date="activityMenuView.addingNoteDate"
|
||||
onDateTimeChanged="activityMenuView.onDateTimeChanged"
|
||||
placeholder="activityMenuView.addingNoteDatePlaceholder"
|
||||
/>
|
||||
</div>
|
||||
<div class="o_note_input_box">
|
||||
<p><input class="o_note_input bg-transparent" type="text" placeholder="Remember..." t-on-keydown="activityMenuView.onKeydownNoteInput" t-ref="noteInput"/></p>
|
||||
<span class="ml8 mr4">
|
||||
<a class="o_note_save" t-on-click="activityMenuView.onClickSaveNote">SAVE</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerPatch } from '@mail/model/model_core';
|
||||
import { attr } from '@mail/model/model_field';
|
||||
|
||||
registerPatch({
|
||||
name: 'ActivityGroup',
|
||||
fields: {
|
||||
isNote: attr({
|
||||
compute() {
|
||||
return this.irModel.model === 'note.note';
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerPatch } from '@mail/model/model_core';
|
||||
import { attr } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
|
||||
const { DateTime } = luxon;
|
||||
const urlRegExp = /http(s)?:\/\/(www\.)?[a-zA-Z0-9@:%_+~#=~#?&/=\-;!.]{3,2000}/g;
|
||||
|
||||
registerPatch({
|
||||
name: 'ActivityMenuView',
|
||||
recordMethods: {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
close() {
|
||||
this.update({
|
||||
addingNoteDoFocus: clear(),
|
||||
isAddingNote: false,
|
||||
});
|
||||
this._super();
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickAddNote(ev) {
|
||||
this.update({
|
||||
addingNoteDoFocus: true,
|
||||
isAddingNote: true,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickSaveNote(ev) {
|
||||
this.saveNote();
|
||||
},
|
||||
onComponentUpdate() {
|
||||
if (this.addingNoteDoFocus && this.noteInputRef.el) {
|
||||
this.noteInputRef.el.focus();
|
||||
this.update({ addingNoteDoFocus: clear() });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {DateTime|string} date
|
||||
*/
|
||||
onDateTimeChanged(date) {
|
||||
this.update({ addingNoteDate: date ? date : clear() });
|
||||
},
|
||||
/**
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
onKeydownNoteInput(ev) {
|
||||
if (ev.key === 'Enter') {
|
||||
this.saveNote();
|
||||
}
|
||||
},
|
||||
async saveNote() {
|
||||
const note = this.noteInputRef.el.value.replace(urlRegExp, '<a href="$&">$&</a>').trim();
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
this.update({ isAddingNote: false });
|
||||
await this.messaging.rpc({
|
||||
route: '/note/new',
|
||||
params: {
|
||||
'note': note,
|
||||
'date_deadline': this.addingNoteDate ? this.addingNoteDate : new DateTime.local(),
|
||||
},
|
||||
});
|
||||
this.fetchData();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onClickCaptureGlobal(ev) {
|
||||
if (ev.target.closest('.bootstrap-datetimepicker-widget')) {
|
||||
return;
|
||||
}
|
||||
this._super(ev);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
activityGroups: {
|
||||
sort() {
|
||||
return [
|
||||
['truthy-first', 'isNote'],
|
||||
...this._super,
|
||||
];
|
||||
},
|
||||
},
|
||||
addingNoteDate: attr(),
|
||||
addingNoteDatePlaceholder: attr({
|
||||
compute() {
|
||||
return this.env._t("Today");
|
||||
},
|
||||
}),
|
||||
addingNoteDoFocus: attr({
|
||||
default: false,
|
||||
}),
|
||||
isAddingNote: attr({
|
||||
default: false,
|
||||
}),
|
||||
noteInputRef: attr(),
|
||||
},
|
||||
});
|
||||
78
odoo-bringout-oca-ocb-note/note/static/src/scss/note.scss
Normal file
78
odoo-bringout-oca-ocb-note/note/static/src/scss/note.scss
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
|
||||
.o_kanban_group .note_text_line_through {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.o_note_form_view {
|
||||
.o_form_sheet_bg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
.o_form_statusbar {
|
||||
margin: 0;
|
||||
}
|
||||
.o_form_sheet {
|
||||
margin: 0;
|
||||
border: none;
|
||||
flex: 1;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
.oe_pad {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.oe_pad_content {
|
||||
border: none;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.note-editable {
|
||||
border: none;
|
||||
padding: $o-sheet-vpadding $o-horizontal-padding 10px !important;
|
||||
min-height: 300px;
|
||||
}
|
||||
}
|
||||
.o_field_widget[name="memo"] {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height: 200px;
|
||||
height: 100%
|
||||
}
|
||||
&.o_form_readonly {
|
||||
.oe_memo {
|
||||
padding: $o-sheet-vpadding $o-horizontal-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.oe_memo {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// Quick create notes from systray
|
||||
.o_note.o_ActivityMenuView_activityGroup {
|
||||
.o_ActivityMenuView_activityGroupInfo {
|
||||
.o_ActivityMenuView_activityGroupTitle {
|
||||
.o_ActivityMenuView_activityGroupName {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
.o_note_input_box {
|
||||
display: flex;
|
||||
p {
|
||||
flex: 1 1 auto;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
.o_note_save {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.o_note_input {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
// ensure mail override is applied first.
|
||||
import '@mail/../tests/helpers/mock_server';
|
||||
|
||||
import { patch } from '@web/core/utils/patch';
|
||||
import { MockServer } from '@web/../tests/helpers/mock_server';
|
||||
|
||||
import { date_to_str } from 'web.time';
|
||||
|
||||
patch(MockServer.prototype, 'note', {
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async _performRPC(route, args) {
|
||||
if (route === '/note/new') {
|
||||
return this._mockRouteNoteNew(args);
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private Mocked Routes
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Simulates the `/note/new` route.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_mockRouteNoteNew(values) {
|
||||
const noteId = this.pyEnv['note.note'].create({ memo: values['note'] });
|
||||
if (values['date_deadline']) {
|
||||
this.pyEnv['mail.activity'].create({
|
||||
date_deadline: date_to_str(new Date(values['date_deadline'])),
|
||||
note_id: noteId,
|
||||
res_model: 'note.note',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private Mocked Methods
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Simulates `systray_get_activities` on `res.users`.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_mockResUsersSystrayGetActivities() {
|
||||
const activities = this._super(...arguments);
|
||||
const noteCount = this.pyEnv['note.note'].searchCount([['user_id', '=', this.currentUserId]]);
|
||||
if (noteCount) {
|
||||
const noteIndex = activities.findIndex(act => act['model'] === 'note.note');
|
||||
if (noteIndex) {
|
||||
activities[noteIndex]['name'] = 'Notes';
|
||||
} else {
|
||||
activities.push({
|
||||
id: 'note.note', // for simplicity
|
||||
type: 'activity',
|
||||
name: 'Notes',
|
||||
model: 'note.note',
|
||||
planned_count: 0,
|
||||
today_count: 0,
|
||||
overdue_count: 0,
|
||||
total_count: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
return activities;
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
|
||||
|
||||
addModelNamesToFetch(['note.note']);
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { start } from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import testUtils from 'web.test_utils';
|
||||
|
||||
QUnit.module('note', {}, function () {
|
||||
QUnit.module("ActivityMenu");
|
||||
|
||||
QUnit.test('note activity menu widget: create note from activity menu', async function (assert) {
|
||||
assert.expect(15);
|
||||
|
||||
const { click } = await start();
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView',
|
||||
'should contain an instance of widget');
|
||||
assert.containsNone(document.body, '.o_ActivityMenuView_counter',
|
||||
"should not have any activity notification initially");
|
||||
|
||||
// toggle quick create for note
|
||||
await click('.dropdown-toggle[title="Activities"]');
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_noActivity',
|
||||
"should not have any activity preview");
|
||||
assert.containsOnce(document.body, '.o_note_show',
|
||||
'ActivityMenu should have Add new note CTA');
|
||||
await click('.o_note_show');
|
||||
assert.containsNone(document.body, '.o_note_show',
|
||||
'ActivityMenu should hide CTA when entering a new note');
|
||||
assert.containsOnce(document.body, '.o_note',
|
||||
'ActivityMenu should display input for new note');
|
||||
|
||||
// creating quick note without date
|
||||
await testUtils.fields.editInput(document.querySelector("input.o_note_input"), "New Note");
|
||||
await click('.o_note_save');
|
||||
assert.strictEqual(document.querySelector('.o_ActivityMenuView_counter').innerText, '1',
|
||||
"should increment activity notification counter after creating a note");
|
||||
assert.containsOnce(document.body, '.o_ActivityMenuView_activityGroup[data-res_model="note.note"]',
|
||||
"should have an activity preview that is a note");
|
||||
assert.strictEqual(document.querySelector('.o_ActivityMenuView_activityGroupFilterButton[data-filter="today"]').innerText.trim(),
|
||||
"1 Today",
|
||||
"should display one note for today");
|
||||
|
||||
assert.doesNotHaveClass(document.querySelector('.o_note_show'), 'd-none',
|
||||
'ActivityMenu add note button should be displayed');
|
||||
assert.containsNone(document.body, '.o_note',
|
||||
'ActivityMenu add note input should be hidden');
|
||||
|
||||
// creating quick note with date
|
||||
await click('.o_note_show');
|
||||
document.querySelector('input.o_note_input').value = "New Note";
|
||||
await click(".o_note_save");
|
||||
assert.strictEqual(document.querySelector('.o_ActivityMenuView_counter').innerText, '2',
|
||||
"should increment activity notification counter after creating a second note");
|
||||
assert.strictEqual(document.querySelector('.o_ActivityMenuView_activityGroupFilterButton[data-filter="today"]').innerText.trim(),
|
||||
"2 Today",
|
||||
"should display 2 notes for today");
|
||||
assert.doesNotHaveClass(document.querySelector('.o_note_show'), 'd-none',
|
||||
'ActivityMenu add note button should be displayed');
|
||||
assert.containsNone(document.body, '.o_note',
|
||||
'ActivityMenu add note input should be hidden');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue