19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:29:53 +01:00
parent 6e54c1af6c
commit 3ca647e428
1087 changed files with 132065 additions and 108499 deletions

View file

@ -0,0 +1,52 @@
import { patch } from "@web/core/utils/patch";
import { hootPosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import { models } from "@web/../tests/web_test_helpers";
const { DateTime } = luxon;
export class LoyaltyCard extends models.ServerModel {
_name = "loyalty.card";
_load_pos_data_fields() {
return ["partner_id", "code", "points", "program_id", "expiration_date", "write_date"];
}
_records = [
{
id: 1,
code: "CARD001",
points: 10,
partner_id: 1,
program_id: 1,
expiration_date: DateTime.now().plus({ days: 1 }).toISODate(),
write_date: DateTime.now().minus({ days: 1 }).toFormat("yyyy-MM-dd HH:mm:ss"),
},
{
id: 2,
code: "CARD002",
points: 25,
partner_id: 1,
program_id: 2,
expiration_date: DateTime.now().minus({ days: 1 }).toISODate(),
write_date: DateTime.now().minus({ days: 2 }).toFormat("yyyy-MM-dd HH:mm:ss"),
},
{
id: 3,
code: "CARD003",
points: 15,
partner_id: 3,
program_id: 3,
write_date: DateTime.now().toFormat("yyyy-MM-dd HH:mm:ss"),
},
{
id: 4,
code: "CARD004",
points: 3,
partner_id: 1,
program_id: 7,
write_date: DateTime.now().minus({ days: 2 }).toFormat("yyyy-MM-dd HH:mm:ss"),
},
];
}
patch(hootPosModels, [...hootPosModels, LoyaltyCard]);

View file

@ -0,0 +1,178 @@
import { patch } from "@web/core/utils/patch";
import { hootPosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import { models } from "@web/../tests/web_test_helpers";
const { DateTime } = luxon;
export class LoyaltyProgram extends models.ServerModel {
_name = "loyalty.program";
_load_pos_data_fields() {
return [
"name",
"trigger",
"applies_on",
"program_type",
"pricelist_ids",
"date_from",
"date_to",
"limit_usage",
"max_usage",
"is_nominative",
"portal_visible",
"portal_point_name",
"trigger_product_ids",
"rule_ids",
"reward_ids",
];
}
_records = [
{
id: 1,
name: "Loyalty Program",
trigger: "auto",
applies_on: "both",
program_type: "loyalty",
pricelist_ids: [1],
date_from: false,
date_to: false,
limit_usage: false,
max_usage: 0,
is_nominative: false,
portal_visible: true,
portal_point_name: "Points",
trigger_product_ids: [],
rule_ids: [1],
reward_ids: [],
},
{
id: 2,
name: "E-Wallet Program",
trigger: "auto",
applies_on: "future",
program_type: "ewallet",
pricelist_ids: [],
date_from: false,
date_to: false,
limit_usage: false,
max_usage: 0,
is_nominative: false,
portal_visible: true,
portal_point_name: "E-Wallet Points",
trigger_product_ids: [],
rule_ids: [],
reward_ids: [],
},
{
id: 3,
name: "Gift Card Program",
trigger: "auto",
applies_on: "future",
program_type: "gift_card",
pricelist_ids: [],
date_from: false,
date_to: false,
limit_usage: false,
max_usage: 0,
is_nominative: false,
portal_visible: true,
portal_point_name: "Gift Card Points",
trigger_product_ids: [],
rule_ids: [1],
reward_ids: [],
},
{
id: 4,
name: "E-Wallet Program 2",
trigger: "auto",
applies_on: "future",
program_type: "ewallet",
pricelist_ids: [],
date_from: false,
date_to: false,
limit_usage: false,
max_usage: 0,
is_nominative: false,
portal_visible: true,
portal_point_name: "E-Wallet Points 2",
trigger_product_ids: [],
rule_ids: [],
reward_ids: [],
},
{
id: 5,
name: "Nominative Gift Card Program",
trigger: "auto",
applies_on: "future",
program_type: "gift_card",
pricelist_ids: [],
date_from: false,
date_to: false,
limit_usage: false,
max_usage: 0,
is_nominative: true,
portal_visible: true,
portal_point_name: "Nominative Gift Card Points",
trigger_product_ids: [],
rule_ids: [1],
reward_ids: [],
},
{
id: 6,
name: "E-Wallet Program 2",
trigger: "auto",
applies_on: "future",
program_type: "ewallet",
pricelist_ids: [],
date_from: DateTime.now().minus({ days: 10 }).toISODate(),
date_to: DateTime.now().minus({ days: 1 }).toISODate(),
limit_usage: false,
max_usage: 0,
is_nominative: false,
portal_visible: true,
portal_point_name: "E-Wallet Points",
trigger_product_ids: [],
rule_ids: [],
reward_ids: [],
},
{
id: 7,
name: "Loyalty Program Future",
trigger: "auto",
applies_on: "future",
program_type: "loyalty",
pricelist_ids: [1],
date_from: false,
date_to: false,
limit_usage: false,
max_usage: 0,
is_nominative: true,
portal_visible: true,
portal_point_name: "Points",
trigger_product_ids: [],
rule_ids: [4],
reward_ids: [3],
},
{
id: 8,
name: "100% Cheapest Discount Program",
trigger: "auto",
applies_on: "current",
program_type: "promotion",
pricelist_ids: [1],
date_from: false,
date_to: false,
limit_usage: false,
max_usage: 0,
is_nominative: false,
portal_visible: true,
portal_point_name: "Points",
trigger_product_ids: [],
rule_ids: [5],
reward_ids: [4],
},
];
}
patch(hootPosModels, [...hootPosModels, LoyaltyProgram]);

View file

@ -0,0 +1,124 @@
import { patch } from "@web/core/utils/patch";
import { hootPosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import { models } from "@web/../tests/web_test_helpers";
export class LoyaltyReward extends models.ServerModel {
_name = "loyalty.reward";
_load_pos_data_fields() {
return [
"description",
"program_id",
"reward_type",
"required_points",
"clear_wallet",
"currency_id",
"discount",
"discount_mode",
"discount_applicability",
"all_discount_product_ids",
"is_global_discount",
"discount_max_amount",
"discount_line_product_id",
"reward_product_id",
"multi_product",
"reward_product_ids",
"reward_product_qty",
"reward_product_uom_id",
"reward_product_domain",
];
}
_records = [
{
id: 1,
description: "10% Discount",
program_id: 1,
reward_type: "discount",
required_points: 10,
clear_wallet: false,
currency_id: 1,
discount: 10,
discount_mode: "percent",
discount_applicability: "order",
all_discount_product_ids: [],
is_global_discount: true,
discount_max_amount: 0,
discount_line_product_id: false,
reward_product_id: false,
multi_product: false,
reward_product_ids: [5],
reward_product_qty: 1,
reward_product_uom_id: false,
reward_product_domain: "[]",
},
{
id: 2,
description: "20% Discount",
program_id: 2,
reward_type: "product",
required_points: 10,
clear_wallet: false,
currency_id: 1,
discount: 0,
discount_mode: "percent",
discount_applicability: "order",
all_discount_product_ids: [],
is_global_discount: true,
discount_max_amount: 0,
discount_line_product_id: false,
reward_product_id: false,
multi_product: false,
reward_product_ids: [5],
reward_product_qty: 1,
reward_product_uom_id: false,
reward_product_domain: "[]",
},
{
id: 3,
description: "Free Product - Whiteboard Pen",
program_id: 7,
reward_type: "product",
required_points: 1,
clear_wallet: false,
currency_id: 1,
discount: 0,
discount_mode: "percent",
discount_applicability: "order",
all_discount_product_ids: [],
is_global_discount: true,
discount_max_amount: 0,
discount_line_product_id: 18,
reward_product_id: 10,
multi_product: false,
reward_product_ids: [10],
reward_product_qty: 1,
reward_product_uom_id: false,
reward_product_domain: "[]",
},
{
id: 4,
description: "100% Cheapest Discount",
program_id: 8,
reward_type: "discount",
required_points: 1,
clear_wallet: false,
currency_id: 1,
discount: 100,
discount_mode: "percent",
discount_applicability: "cheapest",
all_discount_product_ids: [],
is_global_discount: false,
discount_max_amount: 0,
discount_line_product_id: 5,
reward_product_id: false,
multi_product: false,
reward_product_ids: [5],
reward_product_qty: 1,
reward_product_uom_id: false,
reward_product_domain: "[]",
},
];
}
patch(hootPosModels, [...hootPosModels, LoyaltyReward]);

View file

@ -0,0 +1,100 @@
import { patch } from "@web/core/utils/patch";
import { hootPosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
import { models } from "@web/../tests/web_test_helpers";
export class LoyaltyRule extends models.ServerModel {
_name = "loyalty.rule";
_load_pos_data_fields() {
return [
"program_id",
"valid_product_ids",
"any_product",
"currency_id",
"reward_point_amount",
"reward_point_split",
"reward_point_mode",
"minimum_qty",
"minimum_amount",
"minimum_amount_tax_mode",
"mode",
"code",
];
}
_records = [
{
id: 1,
program_id: 1,
valid_product_ids: [5],
any_product: true,
currency_id: 1,
reward_point_amount: 1,
reward_point_split: true,
reward_point_mode: "order",
minimum_qty: 0,
minimum_amount: 0,
minimum_amount_tax_mode: "incl",
mode: "auto",
code: false,
},
{
id: 2,
program_id: 2,
valid_product_ids: [5],
any_product: true,
currency_id: 1,
reward_point_amount: 1,
reward_point_split: true,
reward_point_mode: "order",
minimum_qty: 3,
minimum_amount: 40,
minimum_amount_tax_mode: "excl",
mode: "auto",
code: false,
},
{
id: 3,
program_id: 6,
valid_product_ids: [5],
any_product: true,
currency_id: 1,
reward_point_amount: 1,
reward_point_split: true,
reward_point_mode: "order",
minimum_qty: 3,
minimum_amount: 40,
minimum_amount_tax_mode: "excl",
mode: "with_code",
code: "EXPIRED",
},
{
id: 4,
program_id: 7,
any_product: true,
currency_id: 1,
reward_point_amount: 1,
reward_point_split: false,
reward_point_mode: "unit",
minimum_qty: 1,
minimum_amount: 0,
minimum_amount_tax_mode: "incl",
mode: "auto",
},
{
id: 5,
program_id: 8,
any_product: true,
currency_id: 1,
reward_point_amount: 1,
reward_point_split: false,
reward_point_mode: "unit",
minimum_qty: 1,
minimum_amount: 0,
minimum_amount_tax_mode: "incl",
mode: "auto",
},
];
}
patch(hootPosModels, [...hootPosModels, LoyaltyRule]);

View file

@ -0,0 +1,110 @@
import { patch } from "@web/core/utils/patch";
import { PosOrder } from "@point_of_sale/../tests/unit/data/pos_order.data";
import { _t } from "@web/core/l10n/translation";
patch(PosOrder.prototype, {
validate_coupon_programs(self, point_changes) {
const couponIdsFromPos = new Set(Object.keys(point_changes).map((id) => parseInt(id)));
const coupons = this.env["loyalty.card"]
.browse([...couponIdsFromPos])
.filter((c) => c && c.program_id);
const couponDifference = new Set(
[...couponIdsFromPos].filter((id) => !coupons.find((c) => c.id === id))
);
if (couponDifference.size > 0) {
return {
successful: false,
payload: {
message: _t(
"Some coupons are invalid. The applied coupons have been updated. Please check the order."
),
removed_coupons: [...couponDifference],
},
};
}
for (const coupon of coupons) {
const needed = -point_changes[coupon.id];
if (parseFloat(coupon.points.toFixed(2)) < parseFloat(needed.toFixed(2))) {
return {
successful: false,
payload: {
message: _t("There are not enough points for the coupon: %s.", coupon.code),
updated_points: Object.fromEntries(coupons.map((c) => [c.id, c.points])),
},
};
}
}
return {
successful: true,
payload: {},
};
},
confirm_coupon_programs(self, coupon_data) {
const couponNewIdMap = {};
for (const k of Object.keys(coupon_data)) {
const id = parseInt(k);
if (id > 0) {
couponNewIdMap[id] = id;
}
}
const couponsToCreate = Object.fromEntries(
Object.entries(coupon_data).filter(([k]) => parseInt(k) < 0)
);
const couponCreateVals = Object.values(couponsToCreate).map((p) => ({
program_id: p.program_id,
partner_id: p.partner_id || false,
code: p.code || p.barcode || `CODE${Math.floor(Math.random() * 10000)}`,
points: p.points || 0,
}));
const newCouponIds = this.env["loyalty.card"].create(couponCreateVals);
const newCoupons = this.env["loyalty.card"].browse(newCouponIds);
for (let i = 0; i < Object.keys(couponsToCreate).length; i++) {
const oldId = parseInt(Object.keys(couponsToCreate)[i], 10);
const newCoupon = newCouponIds[i];
couponNewIdMap[oldId] = newCoupon.id;
}
const allCoupons = this.env["loyalty.card"].browse(Object.keys(couponNewIdMap).map(Number));
for (const coupon of allCoupons) {
const oldId = couponNewIdMap[coupon.id];
if (oldId && coupon_data[oldId]) {
coupon.points += coupon_data[oldId].points;
}
}
return {
coupon_updates: allCoupons.map((coupon) => ({
old_id: couponNewIdMap[coupon.id],
id: coupon.id,
points: coupon.points,
code: coupon.code,
program_id: coupon.program_id,
partner_id: coupon.partner_id,
})),
program_updates: [...new Set(allCoupons.map((c) => c.program_id))].map((program) => ({
program_id: program,
usages: this.env["loyalty.program"].browse(program)?.[0]?.total_order_count,
})),
new_coupon_info: newCoupons.map((c) => ({
program_name: this.env["loyalty.program"].browse(c.program_id)?.[0]?.name || "",
expiration_date: c.expiration_date || false,
code: c.code,
})),
coupon_report: {},
};
},
add_loyalty_history_lines() {
return true;
},
});

View file

@ -0,0 +1,15 @@
import { patch } from "@web/core/utils/patch";
import { PosOrderLine } from "@point_of_sale/../tests/unit/data/pos_order_line.data";
patch(PosOrderLine.prototype, {
_load_pos_data_fields() {
return [
...super._load_pos_data_fields(),
"is_reward_line",
"reward_id",
"coupon_id",
"reward_identifier_code",
"points_cost",
];
},
});

View file

@ -0,0 +1,14 @@
import { patch } from "@web/core/utils/patch";
import { PosSession } from "@point_of_sale/../tests/unit/data/pos_session.data";
patch(PosSession.prototype, {
_load_pos_data_models() {
return [
...super._load_pos_data_models(),
"loyalty.card",
"loyalty.program",
"loyalty.reward",
"loyalty.rule",
];
},
});

View file

@ -0,0 +1,8 @@
import { patch } from "@web/core/utils/patch";
import { ProductProduct } from "@point_of_sale/../tests/unit/data/product_product.data";
patch(ProductProduct.prototype, {
_load_pos_data_fields() {
return [...super._load_pos_data_fields(), "all_product_tag_ids"];
},
});