Initial commit: Core packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:45 +02:00
commit 12c29a983b
9512 changed files with 8379910 additions and 0 deletions

View file

@ -0,0 +1,44 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { SelectionField } from "@web/views/fields/selection/selection_field";
/**
* The purpose of this field is to be able to define some values which should not be
* displayed on our selection field, this way we can have multiple views for the same model
* that uses different possible sets of values on the same selection field.
*/
export class FilterableSelectionField extends SelectionField {
/**
* @override
*/
get options() {
let options = super.options;
if (this.props.whitelisted_values) {
options = options.filter((option) => {
return option[0] === this.props.value || this.props.whitelisted_values.includes(option[0])
});
} else if (this.props.blacklisted_values) {
options = options.filter((option) => {
return option[0] === this.props.value || !this.props.blacklisted_values.includes(option[0]);
});
}
return options;
}
};
FilterableSelectionField.props = {
...SelectionField.props,
whitelisted_values: { type: Array, optional: true },
blacklisted_values: { type: Array, optional: true },
};
FilterableSelectionField.extractProps = ({ attrs }) => {
return {
...SelectionField.extractProps({ attrs }),
whitelisted_values: attrs.options.whitelisted_values,
blacklisted_values: attrs.options.blacklisted_values,
};
};
registry.category("fields").add("filterable_selection", FilterableSelectionField);

View file

@ -0,0 +1,11 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { listView } from "@web/views/list/list_view";
export const LoyaltyCardListView = {
...listView,
buttonTemplate: "loyalty.LoyaltyCardListView.buttons",
};
registry.category("views").add("loyalty_card_list_view", LoyaltyCardListView);

View file

@ -0,0 +1,9 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { X2ManyField } from "@web/views/fields/x2many/x2many_field";
export class LoyaltyX2ManyField extends X2ManyField {};
LoyaltyX2ManyField.template = "loyalty.LoyaltyX2ManyField";
registry.category("fields").add("loyalty_one2many", LoyaltyX2ManyField);

View file

@ -0,0 +1,54 @@
/** @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 { useService } from "@web/core/utils/hooks";
const { Component, onWillStart } = owl;
export class LoyaltyActionHelper extends Component {
setup() {
this.orm = useService("orm");
this.action = useService("action");
onWillStart(async () => {
this.loyaltyTemplateData = await this.orm.call(
"loyalty.program",
"get_program_templates",
[],
{
context: this.env.model.root.context,
},
);
});
}
async onTemplateClick(templateId) {
const action = await this.orm.call(
"loyalty.program",
"create_from_template",
[templateId],
{context: this.env.model.root.context},
);
if (!action) {
return;
}
this.action.doAction(action);
}
};
LoyaltyActionHelper.template = "loyalty.LoyaltyActionHelper";
export class LoyaltyListRenderer extends ListRenderer {};
LoyaltyListRenderer.template = "loyalty.LoyaltyListRenderer";
LoyaltyListRenderer.components = {
...LoyaltyListRenderer.components,
LoyaltyActionHelper,
};
export const LoyaltyListView = {
...listView,
Renderer: LoyaltyListRenderer,
};
registry.category("views").add("loyalty_program_list_view", LoyaltyListView);

View file

@ -0,0 +1,72 @@
.o_loyalty_kanban_inline {
width: 100% !important;
.o_kanban_renderer {
padding: 0px !important;
.o_kanban_record {
margin-right: 0px;
margin-left: 0px;
width: 100%;
.o_field_many2many_tags .o_tag span {
// Remove the small ball before the tags
width: auto !important;
height: auto !important;
background-color: none !important;
}
}
}
.o_loyalty_kanban_card_right {
text-align: center;
}
}
.loyalty-templates-container {
pointer-events: auto;
.loyalty-template {
&, * {
transition: all .15s;
}
cursor: pointer !important;
img {
filter: invert(.5);
}
&:hover {
* {
color: #7C6576 !important;
}
background-color: var(--o-color-4);
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.1), 0 2px 2px 0 rgba(0, 0, 0, 0.05);
border-color: #7C6576 !important;
img {
filter: invert(0);
}
.card-body {
background-color: var(--o-color-4) !important;
}
}
}
}
.loyalty-rule-form {
// The base width for this field is 100px which is problematic for us.
.o_field_widget.o_field_monetary.o_input > input {
width: 100%;
}
}
.loyalty-program-list-view .o_view_nocontent{
@include media-breakpoint-down(lg){
height: fit-content;
}
}

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<t t-name="loyalty.LoyaltyListRenderer" t-inherit="web.ListRenderer" t-inherit-mode="primary" owl="1">
<t t-call="web.ActionHelper" position="replace">
<t t-if="showNoContentHelper">
<LoyaltyActionHelper noContentHelp="props.noContentHelp"/>
</t>
</t>
</t>
<t t-name="loyalty.LoyaltyActionHelper" owl="1">
<div class="o_view_nocontent flex-wrap pt-5">
<div class="container">
<div class="o_nocontent_help">
<t t-out="props.noContentHelp"/>
</div>
<div class="row justify-content-center loyalty-templates-container">
<t t-foreach="Object.entries(loyaltyTemplateData)" t-as="data" t-key="data[0]">
<t t-set="loyalty_el_icon" t-value="data[1].icon"/>
<t t-set="loyalty_el_title" t-value="data[1].title"/>
<div class="col-6 col-md-4 col-lg-3 py-4">
<div class="card rounded p-3 d-flex align-items-stretch h-100 loyalty-template" t-on-click.stop.prevent="() => this.onTemplateClick(data[0])">
<div class="row m-0 w-100 h-100">
<div class="col-lg-4 p-0">
<div class="d-flex w-100 h-100 align-items-start justify-content-center display-3 p-3 text-muted">
<img t-attf-src="/loyalty/static/img/{{loyalty_el_icon}}.svg" t-attf-alt="{{loyalty_el_title}}"/>
</div>
</div>
<div class="col-lg-8 p-0">
<div class="card-body d-flex flex-column align-items-start justify-content-start h-100">
<h3 class="card-title" t-out="loyalty_el_title"/>
<p class="card-text" t-out="data[1].description"/>
</div>
</div>
</div>
</div>
</div>
</t>
</div>
</div>
</div>
</t>
<t t-name="loyalty.LoyaltyX2ManyField" owl="1" t-inherit-mode="primary" t-inherit="web.X2ManyField">
<t t-if="displayAddButton" position="replace">
<h4 t-esc="field.string or ''"/>
<t t-if="displayAddButton">
<div class="o_cp_buttons me-0 ms-auto" role="toolbar" aria-label="Control panel buttons" t-ref="buttons">
<div>
<button type="button" class="btn btn-secondary o-kanban-button-new" title="Create record" accesskey="c" t-on-click="() => this.onAdd()">
Add
</button>
</div>
</div>
</t>
</t>
<div role="search" position="attributes">
<attribute name="t-if">props.value.count > props.value.limit</attribute>
</div>
</t>
<t t-name="loyalty.LoyaltyCardListView.buttons" owl="1" t-inherit-mode="primary" t-inherit="web.ListView.Buttons">
<xpath expr="//button[hasclass('o_list_button_add')]" position="replace"/>
<xpath expr="//t[contains(@t-if, 'isExportEnable')]" position="before">
<t t-set="supportedProgramTypes" t-value="['coupons', 'gift_card', 'ewallet']"/>
<button t-if="supportedProgramTypes.includes(props.context.program_type)" type="button" class="btn btn-primary o_loyalty_card_list_button_generate" t-attf-data-tooltip="Generate {{props.context.program_item_name}}"
t-on-click.stop.prevent="() => this.actionService.doAction('loyalty.loyalty_generate_wizard_action', { additionalContext: this.props.context, onClose: () => {this.model.load()} })">
Generate <t t-esc="props.context.program_item_name"/>
</button>
</xpath>
</t>
</odoo>