mirror of
https://github.com/bringout/oca-ocb-pos.git
synced 2026-04-24 01:22:04 +02:00
19.0 vanilla
This commit is contained in:
parent
6e54c1af6c
commit
3ca647e428
1087 changed files with 132065 additions and 108499 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 45 KiB |
|
|
@ -0,0 +1,150 @@
|
|||
import { Component, onMounted, useRef, useState } from "@odoo/owl";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { DateTimeInput } from "@web/core/datetime/datetime_input";
|
||||
import { deserializeDateTime, serializeDate } from "@web/core/l10n/dates";
|
||||
import { usePos } from "@point_of_sale/app/hooks/pos_hook";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { debounce } from "@bus/workers/bus_worker_utils";
|
||||
import { logPosMessage } from "@point_of_sale/app/utils/pretty_console_log";
|
||||
import { roundCurrency } from "@point_of_sale/app/models/utils/currency";
|
||||
|
||||
export class ManageGiftCardPopup extends Component {
|
||||
static template = "pos_loyalty.ManageGiftCardPopup";
|
||||
static components = { Dialog, DateTimeInput };
|
||||
static props = {
|
||||
title: String,
|
||||
placeholder: { type: String, optional: true },
|
||||
rows: { type: Number, optional: true },
|
||||
line: Object,
|
||||
getPayload: Function,
|
||||
close: Function,
|
||||
};
|
||||
static defaultProps = {
|
||||
startingValue: "",
|
||||
placeholder: "",
|
||||
rows: 1,
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.ui = useService("ui");
|
||||
this.dialog = useService("dialog");
|
||||
this.pos = usePos();
|
||||
this.state = useState({
|
||||
lockGiftCardFields: false,
|
||||
loading: false,
|
||||
inputValue: this.props.startingValue,
|
||||
amountValue: this.props.line.prices.total_included.toString(),
|
||||
error: false,
|
||||
amountError: false,
|
||||
expirationDate: luxon.DateTime.now().plus({ year: 1 }),
|
||||
});
|
||||
this.inputRef = useRef("input");
|
||||
this.amountInputRef = useRef("amountInput");
|
||||
this.batchedGiftcardCodeKeydown = debounce(this.checkGiftCard.bind(this), 500);
|
||||
onMounted(this.onMounted);
|
||||
}
|
||||
|
||||
onMounted() {
|
||||
// Removing the main "DateTimeInput" component's class "o_input" and
|
||||
// adding the CSS classes "form-control" and "form-control-lg" for styling the form input with Bootstrap.
|
||||
const expirationDateInput = document.querySelector(".o_exp_date_container").children[1];
|
||||
expirationDateInput.classList.remove("o_input");
|
||||
expirationDateInput.classList.add("form-control", "form-control-lg");
|
||||
this.inputRef.el.focus();
|
||||
}
|
||||
|
||||
onKeydownGiftCardCode() {
|
||||
this.state.loading = true;
|
||||
this.batchedGiftcardCodeKeydown();
|
||||
}
|
||||
|
||||
async checkGiftCard() {
|
||||
try {
|
||||
const code = this.state.inputValue.trim();
|
||||
const result = await this.pos.data.call("loyalty.card", "get_gift_card_status", [
|
||||
code,
|
||||
this.pos.config.id,
|
||||
]);
|
||||
|
||||
if (!result.status) {
|
||||
this.dialog.add(AlertDialog, {
|
||||
title: _t("Invalid Gift Card Code"),
|
||||
body: _t(
|
||||
"This code seems to be invalid, please check the Gift Card code and try again."
|
||||
),
|
||||
});
|
||||
this.state.error = true;
|
||||
this.state.lastCheck = false;
|
||||
this.state.inputValue = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.data["loyalty.card"].length > 0) {
|
||||
const giftCard = result.data["loyalty.card"][0];
|
||||
this.state.amountValue = roundCurrency(
|
||||
giftCard.points?.toString() || "0",
|
||||
this.pos.currency
|
||||
).toString();
|
||||
this.state.lockGiftCardFields = true;
|
||||
|
||||
if (giftCard.expiration_date) {
|
||||
this.state.expirationDate = deserializeDateTime(giftCard.expiration_date);
|
||||
}
|
||||
} else {
|
||||
this.state.lockGiftCardFields = false;
|
||||
}
|
||||
} catch (error) {
|
||||
logPosMessage(
|
||||
"ManageGiftCardPopup",
|
||||
"checkGiftCard",
|
||||
"Error fetching gift card data",
|
||||
false,
|
||||
[error]
|
||||
);
|
||||
this.pos.notification.add({
|
||||
type: "danger",
|
||||
body: _t("An error occurred while checking the gift card."),
|
||||
});
|
||||
} finally {
|
||||
this.state.error = false;
|
||||
this.state.loading = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async addBalance(ev) {
|
||||
if (!this.validateCode()) {
|
||||
return;
|
||||
}
|
||||
this.props.getPayload(
|
||||
this.state.inputValue,
|
||||
parseFloat(this.state.amountValue),
|
||||
this.state.expirationDate ? serializeDate(this.state.expirationDate) : false
|
||||
);
|
||||
this.props.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.props.close();
|
||||
}
|
||||
|
||||
validateCode() {
|
||||
const { inputValue, amountValue } = this.state;
|
||||
if (inputValue.trim() === "") {
|
||||
this.state.error = true;
|
||||
return false;
|
||||
}
|
||||
if (amountValue.trim() === "") {
|
||||
this.state.amountError = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
onExpDateChange(date) {
|
||||
this.state.expirationDate = date;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="pos_loyalty.ManageGiftCardPopup">
|
||||
<Dialog title="props.title" size="'md'">
|
||||
<div class="position-relative">
|
||||
<input id="code"
|
||||
t-att-rows="props.rows"
|
||||
class="form-control form-control-lg mx-auto"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
t-model="state.inputValue"
|
||||
t-ref="input"
|
||||
t-on-keydown="onKeydownGiftCardCode"
|
||||
t-att-placeholder="props.placeholder"
|
||||
t-att-style="state.error ? 'border-color: red;' : ''" />
|
||||
<div t-if="state.loading" class="gift-card-loading pe-3 h-100 position-absolute end-0 top-0 d-flex align-items-center">
|
||||
<i class="fa fa-spinner fa-spin" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 d-flex">
|
||||
<div t-attf-class="col align-items-center d-flex {{!ui.isSmall? 'me-2 w-50': ''}}">
|
||||
<div class="col-form-label text-center pe-0 me-4 fs-5">Amount</div>
|
||||
<div t-attf-class="{{ui.isSmall? 'flex-grow-1' : ''}}">
|
||||
<input id="amount"
|
||||
class="form-control form-control-lg"
|
||||
type="number"
|
||||
t-att-disabled="state.lockGiftCardFields || state.loading"
|
||||
t-model="state.amountValue"
|
||||
t-ref="amountInput"
|
||||
placeholder="Enter amount"
|
||||
t-att-style="state.amountError ? 'border-color: red;' : ''"/>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="!ui.isSmall" class="d-flex ms-2 w-50 o_exp_date_container">
|
||||
<div class="col-form-label text-center pe-0 me-4 fs-5">Expiration</div>
|
||||
<DateTimeInput
|
||||
type="'date'"
|
||||
disabled="state.lockGiftCardFields || state.loading"
|
||||
value="state.expirationDate"
|
||||
onChange.bind="onExpDateChange" />
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="ui.isSmall" class="d-flex my-2 o_exp_date_container">
|
||||
<div class="col-form-label text-center pe-0 me-2 fs-5">Expiration</div>
|
||||
<DateTimeInput
|
||||
type="'date'"
|
||||
value="state.expirationDate"
|
||||
disabled="state.lockGiftCardFields || state.loading"
|
||||
onChange.bind="onExpDateChange" />
|
||||
</div>
|
||||
<t t-set-slot="footer">
|
||||
<button class="btn btn-primary" t-on-click="addBalance" t-att-disabled="state.loading">
|
||||
<t t-if="state.lockGiftCardFields">
|
||||
Add existing Gift Card
|
||||
</t>
|
||||
<t t-else="">
|
||||
Add Balance
|
||||
</t>
|
||||
</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import { DataServiceOptions } from "@point_of_sale/app/models/data_service_options";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(DataServiceOptions.prototype, {
|
||||
get databaseTable() {
|
||||
return {
|
||||
...super.databaseTable,
|
||||
"loyalty.card": {
|
||||
key: "id",
|
||||
condition: (record) =>
|
||||
record
|
||||
.backLink("<-pos.order.line.coupon_id")
|
||||
.find((l) => !l.order_id?.canBeRemovedFromIndexedDB),
|
||||
getRecordsBasedOnLines: (orderlines) =>
|
||||
orderlines.map((line) => line.coupon_id).filter((c) => c),
|
||||
},
|
||||
};
|
||||
},
|
||||
get pohibitedAutoLoadedModels() {
|
||||
return [
|
||||
...super.pohibitedAutoLoadedModels,
|
||||
"loyalty.program",
|
||||
"loyalty.rule",
|
||||
"loyalty.reward",
|
||||
];
|
||||
},
|
||||
get cleanupModels() {
|
||||
return [...super.cleanupModels, "loyalty.program"];
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { Base } from "@point_of_sale/app/models/related_models";
|
||||
|
||||
const { DateTime } = luxon;
|
||||
|
||||
export class LoyaltyCard extends Base {
|
||||
static pythonModel = "loyalty.card";
|
||||
|
||||
isExpired() {
|
||||
// If no expiration date is set, the card is not expired
|
||||
if (!this.expiration_date) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DateTime.fromISO(this.expiration_date).toMillis() < DateTime.now().toMillis();
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("pos_available_models").add(LoyaltyCard.pythonModel, LoyaltyCard);
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,85 @@
|
|||
import { PosOrderline } from "@point_of_sale/app/models/pos_order_line";
|
||||
import { formatCurrency } from "@point_of_sale/app/models/utils/currency";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(PosOrderline, {
|
||||
extraFields: {
|
||||
...(PosOrderline.extraFields || {}),
|
||||
_e_wallet_program_id: {
|
||||
model: "pos.order.line",
|
||||
name: "_e_wallet_program_id",
|
||||
relation: "loyalty.program",
|
||||
type: "many2one",
|
||||
local: true,
|
||||
},
|
||||
gift_code: {
|
||||
model: "pos.order.line",
|
||||
name: "gift_code",
|
||||
type: "char",
|
||||
local: true,
|
||||
},
|
||||
_gift_barcode: {
|
||||
model: "pos.order.line",
|
||||
name: "_gift_barcode",
|
||||
type: "char",
|
||||
local: true,
|
||||
},
|
||||
_gift_card_id: {
|
||||
model: "pos.order.line",
|
||||
name: "_gift_card_id",
|
||||
relation: "loyalty.card",
|
||||
type: "many2one",
|
||||
local: true,
|
||||
},
|
||||
_reward_product_id: {
|
||||
model: "pos.order.line",
|
||||
name: "_reward_product_id",
|
||||
relation: "product.product",
|
||||
type: "many2one",
|
||||
local: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
patch(PosOrderline.prototype, {
|
||||
setOptions(options) {
|
||||
if (options.eWalletGiftCardProgram) {
|
||||
this._e_wallet_program_id = options.eWalletGiftCardProgram;
|
||||
}
|
||||
if (options.giftBarcode) {
|
||||
this._gift_barcode = options.giftBarcode;
|
||||
}
|
||||
if (options.giftCardId) {
|
||||
this._gift_card_id = options.giftCardId;
|
||||
}
|
||||
return super.setOptions(...arguments);
|
||||
},
|
||||
getEWalletGiftCardProgramType() {
|
||||
return this._e_wallet_program_id && this._e_wallet_program_id.program_type;
|
||||
},
|
||||
ignoreLoyaltyPoints({ program }) {
|
||||
return (
|
||||
(["gift_card", "ewallet"].includes(program.program_type) &&
|
||||
this._e_wallet_program_id?.id !== program.id) ||
|
||||
this.settled_invoice_id ||
|
||||
this.settled_order_id
|
||||
);
|
||||
},
|
||||
isGiftCardOrEWalletReward() {
|
||||
const coupon = this.coupon_id;
|
||||
if (!coupon || !this.is_reward_line) {
|
||||
return false;
|
||||
}
|
||||
return ["ewallet", "gift_card"].includes(coupon.program_id?.program_type);
|
||||
},
|
||||
getGiftCardOrEWalletBalance() {
|
||||
const coupon = this.coupon_id;
|
||||
return formatCurrency(coupon?.points || 0, this.currency);
|
||||
},
|
||||
getDisplayClasses() {
|
||||
return {
|
||||
...super.getDisplayClasses(),
|
||||
"fst-italic": this.is_reward_line,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { _t } from "@web/core/l10n/translation";
|
||||
import { usePos } from "@point_of_sale/app/hooks/pos_hook";
|
||||
import { PartnerLine } from "@point_of_sale/app/screens/partner_list/partner_line/partner_line";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { formatFloat } from "@web/core/utils/numbers";
|
||||
|
||||
patch(PartnerLine.prototype, {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.pos = usePos();
|
||||
},
|
||||
_getLoyaltyPointsRepr(loyaltyCard) {
|
||||
const program = loyaltyCard.program_id;
|
||||
if (program.program_type === "ewallet") {
|
||||
return `${program.name}: ${this.env.utils.formatCurrency(loyaltyCard.points)}`;
|
||||
}
|
||||
const balanceRepr = formatFloat(loyaltyCard.points, { digits: [69, 2] });
|
||||
if (program.portal_visible) {
|
||||
return `${balanceRepr} ${program.portal_point_name}`;
|
||||
}
|
||||
return _t("%s Points", balanceRepr);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="pos_loyalty.PartnerLine" t-inherit="point_of_sale.PartnerLine" t-inherit-mode="extension" owl="1">
|
||||
<t t-name="pos_loyalty.PartnerLine" t-inherit="point_of_sale.PartnerLine" t-inherit-mode="extension">
|
||||
<xpath expr="//td[hasclass('partner-line-balance')]" position="inside">
|
||||
<t t-set="_loyaltyCards" t-value="env.pos.getLoyaltyCards(props.partner)" />
|
||||
<t t-set="_loyaltyCards" t-value="pos.getLoyaltyCards(props.partner)" />
|
||||
<t t-foreach="_loyaltyCards" t-as="_loyaltyCard" t-key="_loyaltyCard.id">
|
||||
<div class="pos-right-align">
|
||||
<t t-esc="_getLoyaltyPointsRepr(_loyaltyCard)"/>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { PartnerList } from "@point_of_sale/app/screens/partner_list/partner_list";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(PartnerList.prototype, {
|
||||
/**
|
||||
* Needs to be set to true to show the loyalty points in the partner list.
|
||||
* @override
|
||||
*/
|
||||
get isBalanceDisplayed() {
|
||||
return true;
|
||||
},
|
||||
|
||||
async searchPartner() {
|
||||
const res = await super.searchPartner();
|
||||
const programIds = this.pos.models["loyalty.program"].getAll().map((p) => p.id);
|
||||
const coupons = await this.pos.fetchCoupons(
|
||||
[
|
||||
["partner_id", "in", res.map((partner) => partner.id)],
|
||||
["program_id.active", "=", true],
|
||||
["program_id", "in", programIds],
|
||||
["points", ">", 0],
|
||||
],
|
||||
null
|
||||
);
|
||||
this.pos.computePartnerCouponIds(coupons);
|
||||
return res;
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
import { useState, onWillRender } from "@odoo/owl";
|
||||
import { ControlButtons } from "@point_of_sale/app/screens/product_screen/control_buttons/control_buttons";
|
||||
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { TextInputPopup } from "@point_of_sale/app/components/popups/text_input_popup/text_input_popup";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { SelectionPopup } from "@point_of_sale/app/components/popups/selection_popup/selection_popup";
|
||||
import { makeAwaitable } from "@point_of_sale/app/utils/make_awaitable_dialog";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
patch(ControlButtons.prototype, {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.state = useState({
|
||||
nbrRewards: 0,
|
||||
});
|
||||
|
||||
onWillRender(() => {
|
||||
this.state.nbrRewards = this.getPotentialRewards().length;
|
||||
});
|
||||
},
|
||||
_getEWalletRewards(order) {
|
||||
const claimableRewards = order.getClaimableRewards();
|
||||
return claimableRewards.filter((reward_line) => {
|
||||
const coupon = this.pos.models["loyalty.card"].get(reward_line.coupon_id);
|
||||
return (
|
||||
coupon &&
|
||||
reward_line.reward.program_id.program_type == "ewallet" &&
|
||||
!coupon.isExpired()
|
||||
);
|
||||
});
|
||||
},
|
||||
_getEWalletPrograms() {
|
||||
return this.pos.models["loyalty.program"].filter((p) => p.program_type == "ewallet");
|
||||
},
|
||||
async onClickWallet() {
|
||||
const order = this.pos.getOrder();
|
||||
const eWalletPrograms = this._getEWalletPrograms();
|
||||
const orderTotal = order.priceIncl;
|
||||
const eWalletRewards = this._getEWalletRewards(order);
|
||||
if (eWalletRewards.length === 0 && orderTotal >= 0) {
|
||||
this.dialog.add(AlertDialog, {
|
||||
title: _t("No valid eWallet found"),
|
||||
body: _t("Please select a customer and a valid eWallet."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (orderTotal < 0 && eWalletPrograms.length >= 1) {
|
||||
let selectedProgram = null;
|
||||
if (eWalletPrograms.length == 1) {
|
||||
selectedProgram = eWalletPrograms[0];
|
||||
} else {
|
||||
selectedProgram = await makeAwaitable(this.dialog, SelectionPopup, {
|
||||
title: _t("Refund with eWallet"),
|
||||
list: eWalletPrograms.map((program) => ({
|
||||
id: program.id,
|
||||
item: program,
|
||||
label: program.name,
|
||||
})),
|
||||
});
|
||||
}
|
||||
if (selectedProgram) {
|
||||
this.pos.addLineToCurrentOrder(
|
||||
{
|
||||
product_id: selectedProgram.trigger_product_ids[0],
|
||||
product_tmpl_id: selectedProgram.trigger_product_ids[0].product_tmpl_id,
|
||||
_e_wallet_program_id: selectedProgram,
|
||||
price_unit: -orderTotal,
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
} else if (eWalletRewards.length >= 1) {
|
||||
let eWalletReward = null;
|
||||
if (eWalletRewards.length == 1) {
|
||||
eWalletReward = eWalletRewards[0];
|
||||
} else {
|
||||
eWalletReward = await makeAwaitable(this.dialog, SelectionPopup, {
|
||||
title: _t("Use eWallet to pay"),
|
||||
list: eWalletRewards.map(({ reward, coupon_id }) => ({
|
||||
id: reward.id,
|
||||
item: { reward, coupon_id },
|
||||
label: `${reward.description} (${reward.program_id.name})`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
if (eWalletReward) {
|
||||
const result = order._applyReward(
|
||||
eWalletReward.reward,
|
||||
eWalletReward.coupon_id,
|
||||
{}
|
||||
);
|
||||
if (result !== true) {
|
||||
// Returned an error
|
||||
this.dialog.add(AlertDialog, {
|
||||
title: _t("Error"),
|
||||
body: result,
|
||||
});
|
||||
}
|
||||
this.pos.updateRewards();
|
||||
}
|
||||
}
|
||||
},
|
||||
async clickPromoCode() {
|
||||
this.dialog.add(TextInputPopup, {
|
||||
title: _t("Enter Code"),
|
||||
placeholder: _t("Gift card or Discount code"),
|
||||
getPayload: async (code) => {
|
||||
code = code.trim();
|
||||
if (code !== "") {
|
||||
const res = await this.pos.activateCode(code);
|
||||
if (res !== true) {
|
||||
this.notification.add(res, { type: "danger" });
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
getPotentialRewards() {
|
||||
const order = this.pos.getOrder();
|
||||
// Claimable rewards excluding those from eWallet programs.
|
||||
// eWallet rewards are handled in the eWalletButton.
|
||||
let rewards = [];
|
||||
if (order) {
|
||||
const claimableRewards = order.getClaimableRewards();
|
||||
rewards = claimableRewards.filter(
|
||||
({ reward }) => reward.program_id.program_type !== "ewallet"
|
||||
);
|
||||
}
|
||||
const result = {};
|
||||
const discountRewards = rewards.filter(({ reward }) => reward.reward_type == "discount");
|
||||
const freeProductRewards = rewards.filter(({ reward }) => reward.reward_type == "product");
|
||||
const potentialFreeProductRewards = this.pos.getPotentialFreeProductRewards();
|
||||
const avaiRewards = [
|
||||
...potentialFreeProductRewards,
|
||||
...discountRewards,
|
||||
...freeProductRewards, // Free product rewards at the end of array to prioritize them
|
||||
];
|
||||
|
||||
for (const reward of avaiRewards) {
|
||||
result[reward.reward.id] = reward;
|
||||
}
|
||||
|
||||
return Object.values(result);
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies the reward on the current order, if multiple products can be claimed opens a popup asking for which one.
|
||||
*
|
||||
* @param {Object} reward
|
||||
* @param {Integer} coupon_id
|
||||
*/
|
||||
async _applyReward(reward, coupon_id, potentialQty) {
|
||||
const order = this.pos.getOrder();
|
||||
order.uiState.disabledRewards.delete(reward.id);
|
||||
|
||||
const args = {};
|
||||
if (reward.reward_type === "product" && reward.multi_product) {
|
||||
const productsList = reward.reward_product_ids.map((product_id) => ({
|
||||
id: product_id.id,
|
||||
label: product_id.display_name,
|
||||
item: product_id,
|
||||
}));
|
||||
const selectedProduct = await makeAwaitable(this.dialog, SelectionPopup, {
|
||||
title: _t("Please select a product for this reward"),
|
||||
list: productsList,
|
||||
});
|
||||
if (!selectedProduct) {
|
||||
return false;
|
||||
}
|
||||
args["product"] = selectedProduct;
|
||||
}
|
||||
if (
|
||||
(reward.reward_type == "product" && reward.program_id.applies_on !== "both") ||
|
||||
(reward.program_id.applies_on == "both" && potentialQty)
|
||||
) {
|
||||
const product = args["product"] || reward.reward_product_ids[0];
|
||||
await this.pos.addLineToCurrentOrder(
|
||||
{
|
||||
product_id: product,
|
||||
product_tmpl_id: product.product_tmpl_id,
|
||||
qty: potentialQty || 1,
|
||||
},
|
||||
{}
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
const result = order._applyReward(reward, coupon_id, args);
|
||||
if (result !== true) {
|
||||
// Returned an error
|
||||
this.notification.add(result);
|
||||
}
|
||||
this.pos.updateRewards();
|
||||
return result;
|
||||
}
|
||||
},
|
||||
async clickRewards() {
|
||||
const rewards = this.getPotentialRewards();
|
||||
if (rewards.length >= 1) {
|
||||
const rewardsList = rewards.map((reward) => ({
|
||||
id: reward.reward.id,
|
||||
label: reward.reward.program_id.name,
|
||||
description: `Add "${reward.reward.description}"`,
|
||||
item: reward,
|
||||
}));
|
||||
this.dialog.add(SelectionPopup, {
|
||||
title: _t("Available rewards"),
|
||||
list: rewardsList,
|
||||
getPayload: (selectedReward) => {
|
||||
this._applyReward(
|
||||
selectedReward.reward,
|
||||
selectedReward.coupon_id,
|
||||
selectedReward.potentialQty
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="pos_loyalty.ControlButtons" t-inherit="point_of_sale.ControlButtons" t-inherit-mode="extension">
|
||||
<xpath
|
||||
expr="//t[@t-if='props.showRemainingButtons']/div/button[hasclass('o_pricelist_button')]"
|
||||
position="before">
|
||||
<t t-if="pos.models['loyalty.program'].some((p) => p.program_type == 'ewallet')">
|
||||
<t t-set="_orderTotal" t-value="pos.getOrder().priceIncl" />
|
||||
<t t-set="_eWalletPrograms" t-value="_getEWalletPrograms()" />
|
||||
<t t-set="_eWalletRewards" t-value="_getEWalletRewards(pos.getOrder())" />
|
||||
<button t-att-class="buttonClass"
|
||||
t-on-click="onClickWallet"
|
||||
t-attf-class="{{(_orderTotal lt 0 and _eWalletPrograms.length gte 1) or _eWalletRewards.length gte 1 ? 'highlight text-action': 'disabled'}}">
|
||||
<i class="fa fa-credit-card me-1" />
|
||||
<t t-if="_orderTotal lt 0 and _eWalletPrograms.length">eWallet Refund</t>
|
||||
<t t-elif="_eWalletRewards.length">eWallet Pay</t>
|
||||
<t t-else="">eWallet</t>
|
||||
</button>
|
||||
</t>
|
||||
<t t-if="pos.models['loyalty.program'].some((p) => ['coupons', 'promotion', 'gift_card', 'promo_code', 'next_order_coupons'].includes(p.program_type))">
|
||||
<button t-att-class="buttonClass"
|
||||
t-on-click="() => this.clickPromoCode()">
|
||||
<i class="fa fa-barcode me-1"/>Enter Code
|
||||
</button>
|
||||
</t>
|
||||
<t t-if="pos.models['loyalty.program'].length and this.pos.cashier._role !== 'minimal'">
|
||||
<button class="control-button"
|
||||
t-att-class="buttonClass"
|
||||
t-attf-class="{{state.nbrRewards ? 'highlight' : 'disabled'}}"
|
||||
t-on-click="() => this.clickRewards()">
|
||||
<i class="fa fa-star me-1 text-favourite"/>Reward
|
||||
</button>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-if='props.showRemainingButtons']/div/button[hasclass('o_pricelist_button')]" position="before">
|
||||
<t t-if="pos.models['loyalty.program'].some((p) => ['coupons', 'promotion'].includes(p.program_type)) and this.pos.cashier._role !== 'minimal'">
|
||||
<button class="btn btn-secondary btn-lg py-5" t-att-class="{'disabled': !pos.getOrder().isProgramsResettable()}"
|
||||
t-on-click="() => this.pos.resetPrograms()">
|
||||
<i class="fa fa-star me-1"/>Reset Programs
|
||||
</button>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//button[hasclass('more-btn')]" position="attributes">
|
||||
<attribute name="t-attf-class">{{ state.nbrRewards ? 'active text-action' : '' }}</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
import { _t } from "@web/core/l10n/translation";
|
||||
import { OrderSummary } from "@point_of_sale/app/screens/product_screen/order_summary/order_summary";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { ask } from "@point_of_sale/app/utils/make_awaitable_dialog";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { AlertDialog, ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { ManageGiftCardPopup } from "@pos_loyalty/app/components/popups/manage_giftcard_popup/manage_giftcard_popup";
|
||||
import { logPosMessage } from "@point_of_sale/app/utils/pretty_console_log";
|
||||
|
||||
patch(OrderSummary.prototype, {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.notification = useService("notification");
|
||||
},
|
||||
async updateSelectedOrderline({ buffer, key }) {
|
||||
const selectedLine = this.currentOrder.getSelectedOrderline();
|
||||
if (selectedLine?.gift_code && key !== "Backspace" && key !== "Delete") {
|
||||
this.dialog.add(AlertDialog, {
|
||||
title: _t("Gift Card"),
|
||||
body: _t("You cannot change the quantity or price of a physical gift card."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "-") {
|
||||
if (selectedLine && selectedLine._e_wallet_program_id) {
|
||||
// Do not allow negative quantity or price in a gift card or ewallet orderline.
|
||||
// Refunding gift card or ewallet is not supported.
|
||||
this.notification.add(
|
||||
_t("You cannot set negative quantity or price to gift card or ewallet."),
|
||||
4000
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (
|
||||
selectedLine &&
|
||||
selectedLine.is_reward_line &&
|
||||
!selectedLine.manual_reward &&
|
||||
(key === "Backspace" || key === "Delete")
|
||||
) {
|
||||
const reward = selectedLine.reward_id;
|
||||
const confirmed = await ask(this.dialog, {
|
||||
title: _t("Deactivating reward"),
|
||||
body: _t(
|
||||
"Are you sure you want to remove %s from this order?\n You will still be able to claim it through the reward button.",
|
||||
reward.description
|
||||
),
|
||||
cancelLabel: _t("No"),
|
||||
confirmLabel: _t("Yes"),
|
||||
});
|
||||
if (confirmed) {
|
||||
buffer = null;
|
||||
} else {
|
||||
// Cancel backspace
|
||||
return;
|
||||
}
|
||||
}
|
||||
return super.updateSelectedOrderline({ buffer, key });
|
||||
},
|
||||
/**
|
||||
* 1/ Perform the usual set value operation (super._setValue(val)) if the line being modified
|
||||
* is not a reward line or if it is a reward line, the `val` being set is '' or 'remove' only.
|
||||
*
|
||||
* 2/ Update activated programs and coupons when removing a reward line.
|
||||
*
|
||||
* 3/ Trigger 'update-rewards' if the line being modified is a regular line or
|
||||
* if removing a reward line.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_setValue(val) {
|
||||
const selectedLine = this.currentOrder.getSelectedOrderline();
|
||||
if (!selectedLine) {
|
||||
return;
|
||||
}
|
||||
if (selectedLine.is_reward_line && val === "remove") {
|
||||
this.currentOrder.uiState.disabledRewards.add(selectedLine.reward_id.id);
|
||||
const coupon = selectedLine.coupon_id;
|
||||
if (
|
||||
coupon &&
|
||||
coupon.id > 0 &&
|
||||
this.currentOrder._code_activated_coupon_ids.find((c) => c.code === coupon.code)
|
||||
) {
|
||||
coupon.delete();
|
||||
}
|
||||
}
|
||||
if (
|
||||
!selectedLine ||
|
||||
!selectedLine.is_reward_line ||
|
||||
(selectedLine.is_reward_line && ["", "remove"].includes(val))
|
||||
) {
|
||||
super._setValue(val);
|
||||
}
|
||||
if (!selectedLine.is_reward_line || (selectedLine.is_reward_line && val === "remove")) {
|
||||
this.pos.updateRewards();
|
||||
}
|
||||
},
|
||||
|
||||
async _showDecreaseQuantityPopup() {
|
||||
const result = await super._showDecreaseQuantityPopup();
|
||||
if (result) {
|
||||
this.pos.updateRewards();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the order line with the gift card information:
|
||||
* 1. Reduce the quantity if greater than one, otherwise remove the order line.
|
||||
* 2. Add a new order line with updated gift card code and points, removing any existing related couponPointChanges.
|
||||
*/
|
||||
async _updateGiftCardOrderline(code, points) {
|
||||
let selectedLine = this.currentOrder.getSelectedOrderline();
|
||||
const product = selectedLine.product_id;
|
||||
|
||||
if (selectedLine.getQuantity() > 1) {
|
||||
selectedLine.setQuantity(selectedLine.getQuantity() - 1);
|
||||
} else {
|
||||
this.currentOrder.removeOrderline(selectedLine);
|
||||
}
|
||||
|
||||
const program = this.pos.models["loyalty.program"].find(
|
||||
(p) => p.program_type === "gift_card"
|
||||
);
|
||||
const existingCouponIds = Object.keys(this.currentOrder.uiState.couponPointChanges).filter(
|
||||
(key) => {
|
||||
const change = this.currentOrder.uiState.couponPointChanges[key];
|
||||
return (
|
||||
change.points === product.lst_price &&
|
||||
change.program_id === program.id &&
|
||||
change.product_id === product.id &&
|
||||
!change.manual
|
||||
);
|
||||
}
|
||||
);
|
||||
if (existingCouponIds.length) {
|
||||
const couponId = existingCouponIds.shift();
|
||||
delete this.currentOrder.uiState.couponPointChanges[couponId];
|
||||
}
|
||||
|
||||
await this.pos.addLineToCurrentOrder(
|
||||
{ product_id: product, product_tmpl_id: product.product_tmpl_id },
|
||||
{ price_unit: points }
|
||||
);
|
||||
selectedLine = this.currentOrder.getSelectedOrderline();
|
||||
selectedLine.gift_code = code;
|
||||
},
|
||||
|
||||
manageGiftCard(line) {
|
||||
this.dialog.add(ManageGiftCardPopup, {
|
||||
title: _t("Sell/Manage physical gift card"),
|
||||
placeholder: _t("Enter Gift Card Number"),
|
||||
line: line,
|
||||
getPayload: async (code, points, expirationDate) => {
|
||||
points = parseFloat(points);
|
||||
if (isNaN(points)) {
|
||||
logPosMessage(
|
||||
"OrderSummary",
|
||||
"updateOnlinePaymentsDataWithServer",
|
||||
`Invalid amount value: ${points}`,
|
||||
false
|
||||
);
|
||||
return;
|
||||
}
|
||||
code = code.trim();
|
||||
|
||||
// check for duplicate code
|
||||
if (this.currentOrder.duplicateCouponChanges(code)) {
|
||||
this.dialog.add(ConfirmationDialog, {
|
||||
title: _t("Validation Error"),
|
||||
body: _t("A coupon/loyalty card must have a unique code."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this._updateGiftCardOrderline(code, points);
|
||||
this.currentOrder.processGiftCard(code, points, expirationDate);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
clickLine(ev, orderline) {
|
||||
if (orderline.isSelected() && orderline.getEWalletGiftCardProgramType() === "gift_card") {
|
||||
return;
|
||||
} else {
|
||||
super.clickLine(ev, orderline);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.loyalty-points span.value {
|
||||
width: 80px;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="pos_loyalty.OrderSummary" t-inherit="point_of_sale.OrderSummary" t-inherit-mode="extension">
|
||||
<xpath expr="//Orderline" position="inside" >
|
||||
<li t-if="line.isGiftCardOrEWalletReward()">
|
||||
Current Balance: <t t-esc="line.getGiftCardOrEWalletBalance()"/>
|
||||
</li>
|
||||
<t t-if="!line.isGiftCardOrEWalletReward() and line.getEWalletGiftCardProgramType() === 'gift_card'">
|
||||
<a t-if="!line.gift_code and line.qty <= 1" class="text-wrap text-action" t-on-click="() => this.manageGiftCard(line)">Sell physical gift card?</a>
|
||||
<div t-if="line.gift_code" class="text-wrap" t-esc="line.gift_code"/>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//OrderDisplay/t[@t-set-slot='details']" position="inside">
|
||||
<t t-foreach="pos.getOrder()?.getLoyaltyPoints() or []" t-as="_loyaltyStat" t-key="_loyaltyStat.couponId">
|
||||
<div t-if="_loyaltyStat.points.won || _loyaltyStat.points.spent" class="d-flex justify-content-between px-2 py-2 bg-view">
|
||||
<div t-esc="_loyaltyStat.points.name" class="loyalty-points-title fs-4 fw-bolder"/>
|
||||
<div class="d-flex gap-1 fw-bold">
|
||||
<div t-if='_loyaltyStat.points.balance' class="loyalty-points loyalty-points-balance">
|
||||
<span class='value'><t t-esc='_loyaltyStat.points.balance'/></span>
|
||||
</div>
|
||||
<div t-if='_loyaltyStat.points.won' class="loyalty-points loyalty-points-won">
|
||||
<span class='value text-success'>+<t t-esc='_loyaltyStat.points.won'/></span>
|
||||
</div>
|
||||
<div t-if='_loyaltyStat.points.spent' class="loyalty-points loyalty-points-spent">
|
||||
<span class='value text-danger'>-<t t-esc='_loyaltyStat.points.spent'/></span>
|
||||
</div>
|
||||
=
|
||||
<div class="loyalty-points loyalty-points-totaltext-end fw-bolder">
|
||||
<span class='value'><t t-esc='_loyaltyStat.points.total'/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
|
||||
import { useBarcodeReader } from "@point_of_sale/app/hooks/barcode_reader_hook";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
patch(ProductScreen.prototype, {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.notification = useService("notification");
|
||||
useBarcodeReader({
|
||||
coupon: this._onCouponScan,
|
||||
});
|
||||
},
|
||||
async _onCouponScan(code) {
|
||||
// IMPROVEMENT: Ability to understand if the scanned code is to be paid or to be redeemed.
|
||||
const res = await this.pos.activateCode(code.base_code);
|
||||
if (res !== true) {
|
||||
this.notification.add(res, { type: "danger" });
|
||||
}
|
||||
},
|
||||
async _barcodeProductAction(code) {
|
||||
await super._barcodeProductAction(code);
|
||||
this.pos.updateRewards();
|
||||
},
|
||||
async _barcodeGS1Action(code) {
|
||||
await super._barcodeGS1Action(code);
|
||||
this.pos.updateRewards();
|
||||
},
|
||||
async _barcodePartnerAction(code) {
|
||||
await super._barcodePartnerAction(code);
|
||||
this.pos.updateRewards();
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="pos_coupon.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('pos-receipt')]//div[hasclass('before-footer')]" position="inside">
|
||||
<t t-if="order.models['loyalty.program'].length">
|
||||
<t t-foreach="order.getLoyaltyPoints() or []" t-as="_loyaltyStat" t-key="_loyaltyStat.couponId">
|
||||
<div t-if="_loyaltyStat.program.portal_visible and (_loyaltyStat.points.won || _loyaltyStat.points.spent)" class='pt-3 loyalty'>
|
||||
<t t-if='_loyaltyStat.points.won'>
|
||||
<div class="d-flex" style="font-size: 75%;">
|
||||
<span><t t-out="_loyaltyStat.points.name"/> Won:</span>
|
||||
<span t-out='_loyaltyStat.points.won' class="ms-auto"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if='_loyaltyStat.points.spent'>
|
||||
<div class="d-flex" style="font-size: 75%;">
|
||||
<span><t t-out="_loyaltyStat.points.name"/> Spent:</span>
|
||||
<span t-out='_loyaltyStat.points.spent' class="ms-auto"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if='_loyaltyStat.points.balance'>
|
||||
<div class="d-flex" style="font-size: 75%;">
|
||||
<span>Balance <t t-out="_loyaltyStat.points.name"/>:</span>
|
||||
<span t-out='_loyaltyStat.points.balance' class="ms-auto"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="order.new_coupon_info and order.new_coupon_info.length !== 0">
|
||||
<div class="pos-coupon-rewards pt-3">
|
||||
<t t-foreach="order.new_coupon_info" t-as="coupon_info" t-key="coupon_info.code">
|
||||
<div class="coupon-container">
|
||||
<div class="row g-2">
|
||||
<div class="col-4">
|
||||
<t t-out="coupon_info['program_name']"/>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<img style="transform: translateX(13%);" t-att-src="'/report/barcode/Code128/'+coupon_info['code']" class="img-fluid" alt="Barcode"/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<span>Until: </span>
|
||||
<t t-if="coupon_info['expiration_date']">
|
||||
<t t-out="coupon_info['expiration_date']"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
no expiration
|
||||
</t>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
<t t-out="coupon_info['code']"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { _t } from "@web/core/l10n/translation";
|
||||
import { TicketScreen } from "@point_of_sale/app/screens/ticket_screen/ticket_screen";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
/**
|
||||
* Prevent refunding ewallet/gift card lines.
|
||||
*/
|
||||
patch(TicketScreen.prototype, {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.notification = useService("notification");
|
||||
},
|
||||
async setOrder(order) {
|
||||
await super.setOrder(...arguments);
|
||||
if (order && this.pos.models["loyalty.program"]?.length) {
|
||||
this.pos.updateRewards();
|
||||
}
|
||||
},
|
||||
_onUpdateSelectedOrderline() {
|
||||
const order = this.getSelectedOrder();
|
||||
if (!order) {
|
||||
return this.numberBuffer.reset();
|
||||
}
|
||||
const selectedOrderlineId = this.getSelectedOrderlineId();
|
||||
const orderline = order.lines.find((line) => line.id == selectedOrderlineId);
|
||||
if (orderline && this._isEWalletGiftCard(orderline)) {
|
||||
this._showNotAllowedRefundNotification();
|
||||
return this.numberBuffer.reset();
|
||||
}
|
||||
return super._onUpdateSelectedOrderline(...arguments);
|
||||
},
|
||||
_prepareAutoRefundOnOrder(order) {
|
||||
const selectedOrderlineId = this.getSelectedOrderlineId();
|
||||
const orderline = order.lines.find((line) => line.id == selectedOrderlineId);
|
||||
if (this._isEWalletGiftCard(orderline)) {
|
||||
this._showNotAllowedRefundNotification();
|
||||
return false;
|
||||
}
|
||||
return super._prepareAutoRefundOnOrder(...arguments);
|
||||
},
|
||||
_showNotAllowedRefundNotification() {
|
||||
this.notification.add(
|
||||
_t(
|
||||
"Refunding a top up or reward product for an eWallet or gift card program is not allowed."
|
||||
),
|
||||
5000
|
||||
);
|
||||
},
|
||||
_isEWalletGiftCard(orderline) {
|
||||
if (orderline.is_reward_line) {
|
||||
const reward = orderline.reward_id;
|
||||
const program = reward && reward.program_id;
|
||||
if (program && ["gift_card", "ewallet"].includes(program.program_type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,873 @@
|
|||
import { patch } from "@web/core/utils/patch";
|
||||
import { PosStore } from "@point_of_sale/app/services/pos_store";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { SelectionPopup } from "@point_of_sale/app/components/popups/selection_popup/selection_popup";
|
||||
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { Domain, InvalidDomainError } from "@web/core/domain";
|
||||
import { ask, makeAwaitable } from "@point_of_sale/app/utils/make_awaitable_dialog";
|
||||
import { Mutex } from "@web/core/utils/concurrency";
|
||||
import { serializeDate } from "@web/core/l10n/dates";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
|
||||
let nextId = -1;
|
||||
const mutex = new Mutex();
|
||||
const updateRewardsMutex = new Mutex();
|
||||
const updateProgramsMutex = new Mutex();
|
||||
const pointsForProgramsCountedRules = {};
|
||||
const { DateTime } = luxon;
|
||||
|
||||
export function loyaltyIdsGenerator() {
|
||||
return nextId--;
|
||||
}
|
||||
|
||||
function inverted(fn) {
|
||||
return (arg) => !fn(arg);
|
||||
}
|
||||
|
||||
patch(PosStore.prototype, {
|
||||
async setup() {
|
||||
this.couponByLineUuidCache = {};
|
||||
this.rewardProductByLineUuidCache = {};
|
||||
await super.setup(...arguments);
|
||||
await this.updateOrder(this.getOrder());
|
||||
},
|
||||
async afterProcessServerData() {
|
||||
// Remove reward lines that have no reward anymore (could happen if the program got archived)
|
||||
this.models["pos.order.line"]
|
||||
.filter((order) => order.is_reward_line && !order.reward_id)
|
||||
.map((line) => line.delete());
|
||||
await super.afterProcessServerData(...arguments);
|
||||
},
|
||||
async updateOrder(order) {
|
||||
// Read value to trigger effect
|
||||
order?.lines?.length;
|
||||
await this.orderUpdateLoyaltyPrograms();
|
||||
},
|
||||
async selectPartner(partner) {
|
||||
const res = await super.selectPartner(partner);
|
||||
await this.updateRewards();
|
||||
return res;
|
||||
},
|
||||
async selectPricelist(pricelist) {
|
||||
await super.selectPricelist(pricelist);
|
||||
await this.updateRewards();
|
||||
},
|
||||
async resetPrograms() {
|
||||
const order = this.getOrder();
|
||||
order._resetPrograms();
|
||||
await this.orderUpdateLoyaltyPrograms();
|
||||
await this.updateRewards();
|
||||
},
|
||||
async orderUpdateLoyaltyPrograms() {
|
||||
if (!this.getOrder()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.checkMissingCoupons();
|
||||
await this.updatePrograms();
|
||||
},
|
||||
updateRewards() {
|
||||
// Calls are not expected to take some time besides on the first load + when loyalty programs are made applicable
|
||||
if (this.models["loyalty.program"].length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const order = this.getOrder();
|
||||
if (!order || order.finalized) {
|
||||
return;
|
||||
}
|
||||
updateRewardsMutex.exec(() =>
|
||||
this.orderUpdateLoyaltyPrograms().then(async () => {
|
||||
// Try auto claiming rewards
|
||||
const claimableRewards = order.getClaimableRewards(false, false, true);
|
||||
let changed = false;
|
||||
for (const { coupon_id, reward } of claimableRewards) {
|
||||
if (
|
||||
reward.program_id.reward_ids.length === 1 &&
|
||||
!reward.program_id.is_nominative &&
|
||||
(reward.reward_type !== "product" ||
|
||||
(reward.reward_type == "product" && !reward.multi_product))
|
||||
) {
|
||||
order._applyReward(reward, coupon_id);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const rewardLinesChanged = order._updateRewardLines();
|
||||
|
||||
// Rewards may impact the number of points gained
|
||||
if (changed || rewardLinesChanged) {
|
||||
await this.orderUpdateLoyaltyPrograms();
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
async couponForProgram(program) {
|
||||
const order = this.getOrder();
|
||||
if (program.is_nominative) {
|
||||
return await this.fetchLoyaltyCard(program.id, order.getPartner().id);
|
||||
}
|
||||
// This type of coupons don't need to really exist up until validating the order, so no need to cache
|
||||
return this.models["loyalty.card"].create({
|
||||
id: loyaltyIdsGenerator(),
|
||||
code: null,
|
||||
program_id: program,
|
||||
partner_id: order.partner_id,
|
||||
points: 0,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Update our couponPointChanges, meaning the points/coupons each program give etc.
|
||||
*/
|
||||
async updatePrograms() {
|
||||
return updateProgramsMutex.exec(async () => {
|
||||
await this._updatePrograms();
|
||||
});
|
||||
},
|
||||
|
||||
// This method should never be called directly, use updatePrograms() instead
|
||||
async _updatePrograms() {
|
||||
const order = this.getOrder();
|
||||
// 'order.delivery_provider_id' check is used for UrbanPiper orders (as loyalty points and rewards are not allowed for UrbanPiper orders)
|
||||
if (!order || order.delivery_provider_id) {
|
||||
return;
|
||||
}
|
||||
const changesPerProgram = {};
|
||||
const programsToCheck = new Set();
|
||||
// By default include all programs that are considered 'applicable'
|
||||
for (const program of this.models["loyalty.program"].getAll()) {
|
||||
if (order._programIsApplicable(program)) {
|
||||
programsToCheck.add(program.id);
|
||||
}
|
||||
}
|
||||
for (const pe of Object.values(order.uiState.couponPointChanges)) {
|
||||
if (!changesPerProgram[pe.program_id]) {
|
||||
changesPerProgram[pe.program_id] = [];
|
||||
programsToCheck.add(pe.program_id);
|
||||
}
|
||||
changesPerProgram[pe.program_id].push(pe);
|
||||
}
|
||||
for (const coupon of order._code_activated_coupon_ids) {
|
||||
programsToCheck.add(coupon.program_id.id);
|
||||
}
|
||||
const programs = [...programsToCheck].map((programId) =>
|
||||
this.models["loyalty.program"].get(programId)
|
||||
);
|
||||
const pointsAddedPerProgram = order.pointsForPrograms(programs);
|
||||
for (const program of this.models["loyalty.program"].getAll()) {
|
||||
// Future programs may split their points per unit paid (gift cards for example), consider a non applicable program to give no points
|
||||
const pointsAdded = order._programIsApplicable(program)
|
||||
? pointsAddedPerProgram[program.id]
|
||||
: [];
|
||||
// For programs that apply to both (loyalty) we always add a change of 0 points, if there is none, since it makes it easier to
|
||||
// track for claimable rewards, and makes sure to load the partner's loyalty card.
|
||||
if (program.is_nominative && !pointsAdded.length && order.getPartner()) {
|
||||
pointsAdded.push({ points: 0 });
|
||||
}
|
||||
const oldChanges = changesPerProgram[program.id] || [];
|
||||
// Update point changes for those that exist
|
||||
for (
|
||||
let idx = 0;
|
||||
idx < Math.min(pointsAdded.length, oldChanges.length) && !oldChanges[idx].manual;
|
||||
idx++
|
||||
) {
|
||||
Object.assign(oldChanges[idx], pointsAdded[idx]);
|
||||
}
|
||||
if (pointsAdded.length < oldChanges.length || !order._programIsApplicable(program)) {
|
||||
const removedIds = oldChanges.map((pe) => pe.coupon_id);
|
||||
order.uiState.couponPointChanges = Object.fromEntries(
|
||||
Object.entries(order.uiState.couponPointChanges).filter(
|
||||
([k, pe]) => !removedIds.includes(pe.coupon_id)
|
||||
)
|
||||
);
|
||||
} else if (pointsAdded.length > oldChanges.length) {
|
||||
const pointsCount = pointsAdded.reduce((acc, pointObj) => {
|
||||
const { points, barcode = "" } = pointObj;
|
||||
const key = barcode ? `${points}-${barcode}` : `${points}`;
|
||||
acc[key] = (acc[key] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
oldChanges.forEach((pointObj) => {
|
||||
const { points, barcode = "" } = pointObj;
|
||||
const key = barcode ? `${points}-${barcode}` : `${points}`;
|
||||
if (pointsCount[key] && pointsCount[key] > 0) {
|
||||
pointsCount[key]--;
|
||||
}
|
||||
});
|
||||
|
||||
// Get new points added which are not in oldChanges
|
||||
const newPointsAdded = [];
|
||||
Object.keys(pointsCount).forEach((key) => {
|
||||
const [points, barcode = ""] = key.split("-");
|
||||
while (pointsCount[key] > 0) {
|
||||
newPointsAdded.push({ points: Number(points), barcode });
|
||||
pointsCount[key]--;
|
||||
}
|
||||
});
|
||||
|
||||
for (const pa of newPointsAdded) {
|
||||
const coupon = await this.couponForProgram(program);
|
||||
const couponPointChange = {
|
||||
points: pa.points,
|
||||
program_id: program.id,
|
||||
coupon_id: coupon.id,
|
||||
barcode: pa.barcode,
|
||||
appliedRules: pointsForProgramsCountedRules[program.id],
|
||||
};
|
||||
if (program && program.program_type === "gift_card") {
|
||||
couponPointChange.product_id = order.getSelectedOrderline()?.product_id.id;
|
||||
couponPointChange.expiration_date = serializeDate(
|
||||
luxon.DateTime.now().plus({ year: 1 })
|
||||
);
|
||||
couponPointChange.code = order.getSelectedOrderline()?.gift_code;
|
||||
couponPointChange.partner_id = order.getPartner()?.id;
|
||||
}
|
||||
|
||||
order.uiState.couponPointChanges[coupon.id] = couponPointChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also remove coupons from _code_activated_coupon_ids if their program applies_on current orders and the program does not give any points
|
||||
const toUnlink = order._code_activated_coupon_ids.filter(
|
||||
inverted((coupon) => {
|
||||
const program = coupon.program_id;
|
||||
if (
|
||||
program.applies_on === "current" &&
|
||||
pointsAddedPerProgram[program.id].length === 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
);
|
||||
order._code_activated_coupon_ids = [["unlink", ...toUnlink]];
|
||||
},
|
||||
async activateCode(code) {
|
||||
const order = this.getOrder();
|
||||
const rule = this.models["loyalty.rule"].find(
|
||||
(rule) =>
|
||||
rule.mode === "with_code" && (rule.promo_barcode === code || rule.code === code)
|
||||
);
|
||||
const partnerId = (
|
||||
await this.data.call("loyalty.card", "get_loyalty_card_partner_by_code", [code])
|
||||
)?.[0];
|
||||
let claimableRewards = null;
|
||||
let coupon = null;
|
||||
// If the code belongs to a loyalty card we just set the partner
|
||||
if (partnerId) {
|
||||
if (!this.models["res.partner"].get(partnerId)) {
|
||||
await this.data.read("res.partner", [partnerId]);
|
||||
}
|
||||
const partner = this.models["res.partner"].get(partnerId);
|
||||
order.setPartner(partner);
|
||||
await this.updateRewards();
|
||||
} else if (rule) {
|
||||
const date_order = DateTime.fromSQL(order.date_order);
|
||||
if (
|
||||
rule.program_id.date_from &&
|
||||
date_order < rule.program_id.date_from.startOf("day")
|
||||
) {
|
||||
return _t("That promo code program is not yet valid.");
|
||||
}
|
||||
if (rule.program_id.date_to && date_order > rule.program_id.date_to.endOf("day")) {
|
||||
return _t("That promo code program is expired.");
|
||||
}
|
||||
const program_pricelists = rule.program_id.pricelist_ids;
|
||||
if (
|
||||
program_pricelists.length > 0 &&
|
||||
(!order.pricelist_id ||
|
||||
!program_pricelists.some((pr) => pr.id === order.pricelist_id.id))
|
||||
) {
|
||||
return _t("That promo code program requires a specific pricelist.");
|
||||
}
|
||||
if (order.uiState.codeActivatedProgramRules.includes(rule.id)) {
|
||||
return _t("That promo code program has already been activated.");
|
||||
}
|
||||
order.uiState.codeActivatedProgramRules.push(rule.id);
|
||||
await this.orderUpdateLoyaltyPrograms();
|
||||
claimableRewards = order.getClaimableRewards(false, rule.program_id.id);
|
||||
} else {
|
||||
if (order._code_activated_coupon_ids.find((coupon) => coupon.code === code)) {
|
||||
return _t("That coupon code has already been scanned and activated.");
|
||||
}
|
||||
const customerId = order.getPartner() ? order.getPartner().id : false;
|
||||
const { successful, payload } = await this.data.call("pos.config", "use_coupon_code", [
|
||||
[this.config.id],
|
||||
code,
|
||||
order.date_order,
|
||||
customerId,
|
||||
order.pricelist_id ? order.pricelist_id.id : false,
|
||||
]);
|
||||
if (successful) {
|
||||
// Allow rejecting a gift card that is not yet paid.
|
||||
const program = this.models["loyalty.program"].get(payload.program_id);
|
||||
if (program && program.program_type === "gift_card" && !payload.has_source_order) {
|
||||
const confirmed = await ask(this.dialog, {
|
||||
title: _t("Unpaid gift card"),
|
||||
body: _t(
|
||||
"This gift card is not linked to any order. Do you really want to apply its reward?"
|
||||
),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return _t("Unpaid gift card rejected.");
|
||||
}
|
||||
}
|
||||
// TODO JCB: It's possible that the coupon is already loaded. We should check for that.
|
||||
// - At the moment, creating a new one with existing id creates a duplicate.
|
||||
coupon = this.models["loyalty.card"].create({
|
||||
id: payload.coupon_id,
|
||||
code: code,
|
||||
program_id: this.models["loyalty.program"].get(payload.program_id),
|
||||
partner_id: this.models["res.partner"].get(payload.partner_id),
|
||||
points: payload.points,
|
||||
points_display: payload.points_display,
|
||||
// TODO JCB: make the expiration_date work.
|
||||
// expiration_date: payload.expiration_date,
|
||||
});
|
||||
order._code_activated_coupon_ids = [["link", coupon]];
|
||||
await this.orderUpdateLoyaltyPrograms();
|
||||
claimableRewards = order.getClaimableRewards(coupon.id);
|
||||
} else {
|
||||
return payload.error_message;
|
||||
}
|
||||
}
|
||||
if (claimableRewards && claimableRewards.length === 1) {
|
||||
if (
|
||||
claimableRewards[0].reward.reward_type !== "product" ||
|
||||
!claimableRewards[0].reward.multi_product
|
||||
) {
|
||||
order._applyReward(claimableRewards[0].reward, claimableRewards[0].coupon_id);
|
||||
this.updateRewards();
|
||||
}
|
||||
}
|
||||
if (!rule && order.lines.length === 0 && coupon) {
|
||||
return _t("%s: %s\nBalance: %s", coupon.program_id.name, code, coupon.points_display);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
async checkMissingCoupons() {
|
||||
// This function must stay sequential to avoid potential concurrency errors.
|
||||
const order = this.getOrder();
|
||||
await mutex.exec(async () => {
|
||||
if (!order.invalidCoupons) {
|
||||
return;
|
||||
}
|
||||
order.invalidCoupons = false;
|
||||
order.uiState.couponPointChanges = Object.fromEntries(
|
||||
Object.entries(order.uiState.couponPointChanges).filter(([k, pe]) =>
|
||||
this.models["loyalty.card"].get(pe.coupon_id)
|
||||
)
|
||||
);
|
||||
});
|
||||
},
|
||||
async applyDiscount(percent, order = this.getOrder()) {
|
||||
await super.applyDiscount(...arguments);
|
||||
await this.updatePrograms();
|
||||
},
|
||||
async addLineToCurrentOrder(vals, opt = {}, configure = true) {
|
||||
if (!vals.product_tmpl_id && vals.product_id) {
|
||||
vals.product_tmpl_id = vals.product_id.product_tmpl_id;
|
||||
}
|
||||
|
||||
const productTmpl = vals.product_tmpl_id;
|
||||
const productIds = productTmpl.product_variant_ids.map((v) => v.id);
|
||||
const order = this.getOrder();
|
||||
const linkedPrograms = [
|
||||
...new Set(
|
||||
productIds
|
||||
.flatMap(
|
||||
(id) =>
|
||||
this.models["loyalty.program"].getBy("trigger_product_ids", id) || []
|
||||
)
|
||||
.filter((p) => ["gift_card", "ewallet"].includes(p.program_type))
|
||||
),
|
||||
];
|
||||
let selectedProgram = null;
|
||||
if (linkedPrograms.length > 1) {
|
||||
selectedProgram = await makeAwaitable(this.dialog, SelectionPopup, {
|
||||
title: _t("Select program"),
|
||||
list: linkedPrograms.map((program) => ({
|
||||
id: program.id,
|
||||
item: program,
|
||||
label: program.name,
|
||||
})),
|
||||
});
|
||||
if (!selectedProgram) {
|
||||
return;
|
||||
}
|
||||
} else if (linkedPrograms.length === 1) {
|
||||
selectedProgram = linkedPrograms[0];
|
||||
}
|
||||
|
||||
const orderTotal = this.getOrder().priceIncl;
|
||||
if (
|
||||
selectedProgram &&
|
||||
["gift_card", "ewallet"].includes(selectedProgram.program_type) &&
|
||||
orderTotal < 0
|
||||
) {
|
||||
opt.price_unit = -orderTotal;
|
||||
}
|
||||
if (selectedProgram && selectedProgram.program_type == "gift_card") {
|
||||
const shouldProceed = await this._setupGiftCardOptions(selectedProgram, opt);
|
||||
if (!shouldProceed) {
|
||||
return;
|
||||
}
|
||||
} else if (selectedProgram && selectedProgram.program_type == "ewallet") {
|
||||
const shouldProceed = await this.setupEWalletOptions(selectedProgram, opt);
|
||||
if (!shouldProceed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const potentialRewards = this.getPotentialFreeProductRewards();
|
||||
|
||||
// move price_unit from opt to vals
|
||||
if (opt.price_unit !== undefined) {
|
||||
vals.price_unit = opt.price_unit;
|
||||
delete opt.price_unit;
|
||||
}
|
||||
|
||||
const result = await super.addLineToCurrentOrder(vals, opt, configure);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rewardsToApply = [];
|
||||
for (const reward of potentialRewards) {
|
||||
for (const reward_product_id of reward.reward.reward_product_ids) {
|
||||
if (result.product_id.id == reward_product_id.id) {
|
||||
rewardsToApply.push(reward);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.updatePrograms();
|
||||
if (rewardsToApply.length == 1) {
|
||||
const reward = rewardsToApply[0];
|
||||
order._applyReward(reward.reward, reward.coupon_id, {
|
||||
product: result.product_id,
|
||||
});
|
||||
}
|
||||
this.updateRewards();
|
||||
|
||||
return result;
|
||||
},
|
||||
/**
|
||||
* Sets up the options for the gift card product.
|
||||
* @param {object} program
|
||||
* @param {object} options
|
||||
* @returns {Promise<boolean>} whether to proceed with adding the product or not
|
||||
*/
|
||||
async _setupGiftCardOptions(program, options) {
|
||||
options.quantity = 1;
|
||||
options.merge = false;
|
||||
options.eWalletGiftCardProgram = program;
|
||||
|
||||
return true;
|
||||
},
|
||||
async setupEWalletOptions(program, options) {
|
||||
options.quantity = 1;
|
||||
options.merge = false;
|
||||
options.eWalletGiftCardProgram = program;
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* Returns the reward such that when its reward product is added
|
||||
* in the order, it will be added as free. That is, when added,
|
||||
* it comes with the corresponding reward product line.
|
||||
*/
|
||||
async pay() {
|
||||
const currentOrder = this.getOrder();
|
||||
const eWalletLine = currentOrder
|
||||
.getOrderlines()
|
||||
.find((line) => line.getEWalletGiftCardProgramType() === "ewallet");
|
||||
|
||||
if (eWalletLine && !currentOrder.getPartner()) {
|
||||
const confirmed = await ask(this.dialog, {
|
||||
title: _t("Customer needed"),
|
||||
body: _t("eWallet requires a customer to be selected"),
|
||||
});
|
||||
if (confirmed) {
|
||||
await this.selectPartner();
|
||||
}
|
||||
} else {
|
||||
return super.pay(...arguments);
|
||||
}
|
||||
},
|
||||
getPotentialFreeProductRewards() {
|
||||
const order = this.getOrder();
|
||||
const result = [];
|
||||
if (!order) {
|
||||
return result;
|
||||
}
|
||||
const allCouponPrograms = Object.values(order.uiState.couponPointChanges)
|
||||
.map((pe) => ({
|
||||
program_id: pe.program_id,
|
||||
coupon_id: pe.coupon_id,
|
||||
}))
|
||||
.concat(
|
||||
order._code_activated_coupon_ids.map((coupon) => ({
|
||||
program_id: coupon.program_id.id,
|
||||
coupon_id: coupon.id,
|
||||
}))
|
||||
);
|
||||
for (const couponProgram of allCouponPrograms) {
|
||||
const program = this.models["loyalty.program"].get(couponProgram.program_id);
|
||||
if (
|
||||
program.pricelist_ids.length > 0 &&
|
||||
(!order.pricelist_id ||
|
||||
!program.pricelist_ids.some((pl) => pl.id === order.pricelist_id.id))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const points = order._getRealCouponPoints(couponProgram.coupon_id);
|
||||
const hasLine = order.lines.filter((line) => !line.is_reward_line).length > 0;
|
||||
for (const reward of program.reward_ids.filter(
|
||||
(reward) => reward.reward_type == "product"
|
||||
)) {
|
||||
if (points < reward.required_points) {
|
||||
continue;
|
||||
}
|
||||
// Loyalty program (applies_on == 'both') should needs an orderline before it can apply a reward.
|
||||
const considerTheReward =
|
||||
program.applies_on !== "both" || (program.applies_on == "both" && hasLine);
|
||||
if (reward.reward_type === "product" && considerTheReward) {
|
||||
for (const { id } of reward.reward_product_ids) {
|
||||
const product = this.models["product.product"].get(id);
|
||||
const potentialQty = order._computePotentialFreeProductQty(
|
||||
reward,
|
||||
product,
|
||||
points
|
||||
);
|
||||
if (potentialQty > 0) {
|
||||
result.push({
|
||||
coupon_id: couponProgram.coupon_id,
|
||||
reward: reward,
|
||||
potentialQty,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
//@override
|
||||
async processServerData() {
|
||||
await super.processServerData();
|
||||
|
||||
this.partnerId2CouponIds = {};
|
||||
|
||||
this.computeDiscountProductIdsForAllRewards();
|
||||
|
||||
this.models["product.product"].addEventListener(
|
||||
"create",
|
||||
this.computeDiscountProductIdsForAllRewards.bind(this)
|
||||
);
|
||||
|
||||
for (const rule of this.models["loyalty.rule"].getAll()) {
|
||||
rule.validProductIds = new Set(rule.raw.valid_product_ids);
|
||||
}
|
||||
|
||||
this.models["loyalty.card"].addEventListener("create", (records) => {
|
||||
records = records.ids.map((record) => this.models["loyalty.card"].get(record));
|
||||
this.computePartnerCouponIds(records);
|
||||
});
|
||||
this.computePartnerCouponIds();
|
||||
},
|
||||
|
||||
computeDiscountProductIdsForAllRewards(data) {
|
||||
const productModel = this.models["product.product"].toRaw(); // Limit the number of reactivity proxy instances
|
||||
const products = data ? productModel.readMany(data.ids) : productModel.getAll();
|
||||
for (const reward of this.models["loyalty.reward"].getAll()) {
|
||||
this.computeDiscountProductIds(reward, products);
|
||||
}
|
||||
},
|
||||
|
||||
computePartnerCouponIds(loyaltyCards = null) {
|
||||
const cards = loyaltyCards || this.models["loyalty.card"].getAll();
|
||||
for (const card of cards) {
|
||||
if (!card.partner_id || card.id < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.partnerId2CouponIds[card.partner_id.id]) {
|
||||
this.partnerId2CouponIds[card.partner_id.id] = new Set();
|
||||
}
|
||||
|
||||
this.partnerId2CouponIds[card.partner_id.id].add(card.id);
|
||||
}
|
||||
},
|
||||
|
||||
computeDiscountProductIds(reward, products) {
|
||||
const reward_product_domain = JSON.parse(reward.reward_product_domain);
|
||||
if (!reward_product_domain) {
|
||||
return;
|
||||
}
|
||||
|
||||
const domain = new Domain(reward_product_domain);
|
||||
|
||||
try {
|
||||
const existingProduct = reward.all_discount_product_ids;
|
||||
reward.all_discount_product_ids = [
|
||||
...existingProduct,
|
||||
...products.filter((p) => domain.contains(p.raw)),
|
||||
];
|
||||
} catch (error) {
|
||||
if (!(error instanceof InvalidDomainError || error instanceof TypeError)) {
|
||||
throw error;
|
||||
}
|
||||
const index = this.models["loyalty.reward"].indexOf(reward);
|
||||
if (index != -1) {
|
||||
this.dialog.add(AlertDialog, {
|
||||
title: _t("A reward could not be loaded"),
|
||||
body: _t(
|
||||
'The reward "%s" contain an error in its domain, your domain must be compatible with the PoS client',
|
||||
this.models["loyalty.reward"].getAll()[index].description
|
||||
),
|
||||
showReloadButton: true,
|
||||
});
|
||||
|
||||
reward.delete();
|
||||
}
|
||||
}
|
||||
},
|
||||
async initServerData() {
|
||||
await super.initServerData(...arguments);
|
||||
if (this.selectedOrderUuid) {
|
||||
this.updateRewards();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Fetches `loyalty.card` records from the server and adds/updates them in our cache.
|
||||
*
|
||||
* @param {domain} domain For the search
|
||||
* @param {int} limit Default to 1
|
||||
*/
|
||||
async fetchCoupons(domain, limit = 1) {
|
||||
return await this.data.searchRead(
|
||||
"loyalty.card",
|
||||
domain,
|
||||
this.data.fields["loyalty.card"],
|
||||
{ limit }
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Fetches a loyalty card for the given program and partner, put in cache afterwards
|
||||
* if a matching card is found in the cache, that one is used instead.
|
||||
* If no card is found a local only card will be created until the order is validated.
|
||||
*
|
||||
* @param {int} programId
|
||||
* @param {int} partnerId
|
||||
*/
|
||||
async fetchLoyaltyCard(programId, partnerId) {
|
||||
const coupon = this.models["loyalty.card"].find(
|
||||
(c) => c.partner_id?.id === partnerId && c.program_id?.id === programId
|
||||
);
|
||||
if (coupon) {
|
||||
return coupon;
|
||||
}
|
||||
const fetchedCoupons = await this.fetchCoupons([
|
||||
["partner_id", "=", partnerId],
|
||||
["program_id", "=", programId],
|
||||
]);
|
||||
let dbCoupon = fetchedCoupons.length > 0 ? fetchedCoupons[0] : null;
|
||||
if (!dbCoupon) {
|
||||
dbCoupon = await this.models["loyalty.card"].create({
|
||||
id: loyaltyIdsGenerator(),
|
||||
code: null,
|
||||
program_id: this.models["loyalty.program"].get(programId),
|
||||
partner_id: this.models["res.partner"].get(partnerId),
|
||||
points: 0,
|
||||
expiration_date: null,
|
||||
});
|
||||
}
|
||||
return dbCoupon;
|
||||
},
|
||||
getLoyaltyCards(partner) {
|
||||
const loyaltyCards = [];
|
||||
if (this.partnerId2CouponIds[partner.id]) {
|
||||
this.partnerId2CouponIds[partner.id].forEach((couponId) =>
|
||||
loyaltyCards.push(this.models["loyalty.card"].get(couponId))
|
||||
);
|
||||
}
|
||||
return loyaltyCards;
|
||||
},
|
||||
/**
|
||||
* IMPROVEMENT: It would be better to update the local order object instead of creating a new one.
|
||||
* - This way, we don't need to remember the lines linked to negative coupon ids and relink them after pushing the order.
|
||||
*/
|
||||
async preSyncAllOrders(orders) {
|
||||
await super.preSyncAllOrders(orders);
|
||||
|
||||
for (const order of orders) {
|
||||
Object.assign(
|
||||
this.couponByLineUuidCache,
|
||||
order.lines.reduce((agg, line) => {
|
||||
if (line.coupon_id && line.coupon_id.id < 0) {
|
||||
return { ...agg, [line.uuid]: line.coupon_id.id };
|
||||
} else {
|
||||
return agg;
|
||||
}
|
||||
}, {})
|
||||
);
|
||||
Object.assign(
|
||||
this.rewardProductByLineUuidCache,
|
||||
order.lines.reduce((agg, line) => {
|
||||
if (line._reward_product_id) {
|
||||
return { ...agg, [line.uuid]: line._reward_product_id.id };
|
||||
} else {
|
||||
return agg;
|
||||
}
|
||||
}, {})
|
||||
);
|
||||
}
|
||||
},
|
||||
async postSyncAllOrders(orders) {
|
||||
super.postSyncAllOrders(orders);
|
||||
|
||||
for (const order of orders) {
|
||||
for (const line of order.lines) {
|
||||
if (line.uuid in this.couponByLineUuidCache) {
|
||||
line.coupon_id = this.models["loyalty.card"].get(
|
||||
this.couponByLineUuidCache[line.uuid]
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const line of order.lines) {
|
||||
if (line.uuid in this.rewardProductByLineUuidCache) {
|
||||
line._reward_product_id = this.models["product.product"].get(
|
||||
this.rewardProductByLineUuidCache[line.uuid]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!["draft", "cancel"].includes(order.state)) {
|
||||
await this.postProcessLoyalty(order);
|
||||
}
|
||||
}
|
||||
},
|
||||
async postProcessLoyalty(order) {
|
||||
// Compile data for our function
|
||||
const ProgramModel = this.models["loyalty.program"];
|
||||
const rewardLines = order._get_reward_lines();
|
||||
const partner = order.getPartner();
|
||||
let couponData = Object.values(order.uiState.couponPointChanges).reduce((agg, pe) => {
|
||||
agg[pe.coupon_id] = Object.assign({}, pe, {
|
||||
points: pe.points - order._getPointsCorrection(ProgramModel.get(pe.program_id)),
|
||||
});
|
||||
const program = ProgramModel.get(pe.program_id);
|
||||
if (
|
||||
(program.is_nominative || program.program_type == "next_order_coupons") &&
|
||||
partner
|
||||
) {
|
||||
agg[pe.coupon_id].partner_id = partner.id;
|
||||
}
|
||||
if (program.program_type != "loyalty") {
|
||||
agg[pe.coupon_id].expiration_date = program.date_to || pe.expiration_date;
|
||||
}
|
||||
return agg;
|
||||
}, {});
|
||||
for (const line of rewardLines) {
|
||||
const reward = line.reward_id;
|
||||
const couponId = line.coupon_id.id;
|
||||
if (!couponData[couponId]) {
|
||||
couponData[couponId] = {
|
||||
points: 0,
|
||||
program_id: reward.program_id.id,
|
||||
coupon_id: couponId,
|
||||
barcode: false,
|
||||
};
|
||||
if (reward.program_type != "loyalty") {
|
||||
couponData[couponId].expiration_date = reward.program_id.date_to;
|
||||
}
|
||||
}
|
||||
if (!couponData[couponId].line_codes) {
|
||||
couponData[couponId].line_codes = [];
|
||||
}
|
||||
if (!couponData[couponId].line_codes.includes(line.reward_identifier_code)) {
|
||||
!couponData[couponId].line_codes.push(line.reward_identifier_code);
|
||||
}
|
||||
couponData[couponId].points -= line.points_cost;
|
||||
}
|
||||
// We actually do not care about coupons for 'current' programs that did not claim any reward, they will be lost if not validated
|
||||
couponData = Object.fromEntries(
|
||||
Object.entries(couponData)
|
||||
.filter(([key, value]) => {
|
||||
const program = ProgramModel.get(value.program_id);
|
||||
if (program.applies_on === "current") {
|
||||
return value.line_codes && value.line_codes.length;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(([key, value]) => [key, omit(value, "appliedRules")])
|
||||
);
|
||||
if (Object.keys(couponData || {}).length > 0) {
|
||||
const payload = await this.data.call("pos.order", "confirm_coupon_programs", [
|
||||
order.id,
|
||||
couponData,
|
||||
]);
|
||||
if (payload.coupon_updates) {
|
||||
for (const couponUpdate of payload.coupon_updates) {
|
||||
// The following code is a workaround to update the id of an existing record.
|
||||
// It's so ugly.
|
||||
// FIXME: Find a better way of updating the id of an existing record.
|
||||
// It would be better if we can do this:
|
||||
// const coupon = this.models["loyalty.card"].get(couponUpdate.old_id);
|
||||
// coupon.update({ id: couponUpdate.id, points: couponUpdate.points })
|
||||
|
||||
if (couponUpdate.old_id == couponUpdate.id) {
|
||||
// just update the points
|
||||
const coupon = this.models["loyalty.card"].get(couponUpdate.id);
|
||||
|
||||
if (!coupon) {
|
||||
await this.data.read("loyalty.card", [couponUpdate.id]);
|
||||
} else {
|
||||
coupon.points = couponUpdate.points;
|
||||
}
|
||||
} else {
|
||||
// create a new coupon and delete the old one
|
||||
const coupon = this.models["loyalty.card"].create({
|
||||
id: couponUpdate.id,
|
||||
code: couponUpdate.code,
|
||||
program_id: this.models["loyalty.program"].get(couponUpdate.program_id),
|
||||
partner_id: this.models["res.partner"].get(couponUpdate.partner_id),
|
||||
points: couponUpdate.points,
|
||||
});
|
||||
|
||||
// Before deleting the old coupon, update the order lines that use it.
|
||||
for (const line of order.lines) {
|
||||
if (line.coupon_id?.id == couponUpdate.old_id) {
|
||||
line.coupon_id = coupon;
|
||||
}
|
||||
}
|
||||
|
||||
this.models["loyalty.card"].get(couponUpdate.old_id)?.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update the usage count since it is checked based on local data
|
||||
if (payload.program_updates) {
|
||||
for (const programUpdate of payload.program_updates) {
|
||||
const program = ProgramModel.get(programUpdate.program_id);
|
||||
if (program) {
|
||||
program.total_order_count = programUpdate.usages;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (payload.coupon_report && Object.keys(payload.coupon_report).length > 0) {
|
||||
for (const [actionId, active_ids] of Object.entries(payload.coupon_report)) {
|
||||
await this.env.services.report.doAction(actionId, active_ids);
|
||||
}
|
||||
order.has_pdf_gift_card = Object.keys(payload.coupon_report).length > 0;
|
||||
}
|
||||
if (payload.new_coupon_info?.length) {
|
||||
order.new_coupon_info = payload.new_coupon_info;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import OrderPaymentValidation from "@point_of_sale/app/utils/order_payment_validation";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
|
||||
patch(OrderPaymentValidation.prototype, {
|
||||
async validateOrder(isForceValidate) {
|
||||
const pointChanges = {};
|
||||
const newCodes = [];
|
||||
for (const pe of Object.values(this.order.uiState.couponPointChanges)) {
|
||||
if (pe.coupon_id > 0) {
|
||||
pointChanges[pe.coupon_id] = pe.points;
|
||||
} else if (pe.barcode && !pe.giftCardId) {
|
||||
// New coupon with a specific code, validate that it does not exist
|
||||
newCodes.push(pe.barcode);
|
||||
}
|
||||
}
|
||||
for (const line of this.order._get_reward_lines()) {
|
||||
if (line.coupon_id.id < 1) {
|
||||
continue;
|
||||
}
|
||||
if (!pointChanges[line.coupon_id.id]) {
|
||||
pointChanges[line.coupon_id.id] = -line.points_cost;
|
||||
} else {
|
||||
pointChanges[line.coupon_id.id] -= line.points_cost;
|
||||
}
|
||||
}
|
||||
if (!(await this.isOrderValid(isForceValidate))) {
|
||||
return;
|
||||
}
|
||||
// No need to do an rpc if no existing coupon is being used.
|
||||
if (Object.keys(pointChanges || {}).length > 0 || newCodes.length) {
|
||||
try {
|
||||
const { successful, payload } = await this.pos.data.call(
|
||||
"pos.order",
|
||||
"validate_coupon_programs",
|
||||
[[], pointChanges, newCodes]
|
||||
);
|
||||
// Payload may contain the points of the concerned coupons to be updated in case of error. (So that rewards can be corrected)
|
||||
if (payload && payload.updated_points) {
|
||||
for (const pointChange of Object.entries(payload.updated_points)) {
|
||||
const coupon = this.pos.models["loyalty.card"].get(pointChange[0]);
|
||||
if (coupon) {
|
||||
coupon.points = pointChange[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (payload && payload.removed_coupons) {
|
||||
for (const couponId of payload.removed_coupons) {
|
||||
const coupon = this.pos.models["loyalty.card"].get(couponId);
|
||||
coupon && coupon.delete();
|
||||
}
|
||||
}
|
||||
if (!successful) {
|
||||
this.pos.dialog.add(AlertDialog, {
|
||||
title: _t("Error validating rewards"),
|
||||
body: payload.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Do nothing with error, while this validation step is nice for error messages
|
||||
// it should not be blocking.
|
||||
}
|
||||
}
|
||||
await super.validateOrder(...arguments);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,136 +1,3 @@
|
|||
.pos .popups .gift-card-popup {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
text-align: center;
|
||||
margin: 1em;
|
||||
|
||||
.gift-card-container {
|
||||
display: flex;
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gift-card-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gift-card-input-amount {
|
||||
border-bottom: 1px solid;
|
||||
background-color: #F0EEEE;
|
||||
box-shadow: none;
|
||||
text-align: right;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.gift-card-input-code {
|
||||
border-bottom: 1px solid;
|
||||
background-color: #F0EEEE;
|
||||
box-shadow: none;
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.gift-card-button {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.gift-card-button-confirm {
|
||||
flex-grow: 1;
|
||||
height: auto !important;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.gift-card-input-container {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.footer .gift-card-footer-button {
|
||||
margin-left: 5px;
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.gift-card-error {
|
||||
width: 100%;
|
||||
background-color: rgba(255, 76, 76, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.pos .order {
|
||||
|
||||
.active-programs {
|
||||
padding-top: 0.5em;
|
||||
font-weight: normal;
|
||||
font-size: 80%;
|
||||
text-align: right;
|
||||
padding: 0 1rem .5rem;
|
||||
|
||||
.title {
|
||||
padding-top: 0.8em;
|
||||
padding-bottom: 0.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.orderline.program-reward {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.summary {
|
||||
.loyalty-points {
|
||||
float: left;
|
||||
padding: 10px;
|
||||
max-width: 216px;
|
||||
text-align: left;
|
||||
color: #6EC89B;
|
||||
background: rgba(110, 200, 155, 0.17);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.loyalty-points-title {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.loyalty-points-balance {
|
||||
color: #714B67;
|
||||
}
|
||||
|
||||
.loyalty-points-spent {
|
||||
color: #C86E6E;
|
||||
}
|
||||
|
||||
.loyalty-points-total {
|
||||
border-top: solid 2px;
|
||||
text-align: center;
|
||||
padding-top: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pos-receipt .pos-coupon-rewards {
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.pos-coupon-rewards .coupon-container {
|
||||
padding-top: 1em;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
// Seems unused
|
||||
.loyalty .subtitle{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loyalty .title {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import PosComponent from 'point_of_sale.PosComponent';
|
||||
import ProductScreen from 'point_of_sale.ProductScreen';
|
||||
import Registries from 'point_of_sale.Registries';
|
||||
import { useListener } from "@web/core/utils/hooks";
|
||||
|
||||
export class PromoCodeButton extends PosComponent {
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this.onClick);
|
||||
}
|
||||
|
||||
async onClick() {
|
||||
let { confirmed, payload: code } = await this.showPopup('TextInputPopup', {
|
||||
title: this.env._t('Enter Code'),
|
||||
startingValue: '',
|
||||
placeholder: this.env._t('Gift card or Discount code'),
|
||||
});
|
||||
if (confirmed) {
|
||||
code = code.trim();
|
||||
if (code !== '') {
|
||||
this.env.pos.get_order().activateCode(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PromoCodeButton.template = 'PromoCodeButton';
|
||||
|
||||
ProductScreen.addControlButton({
|
||||
component: PromoCodeButton,
|
||||
condition: function () {
|
||||
return this.env.pos.programs.some(p => ['coupons', 'promotion', 'gift_card', 'promo_code', 'next_order_coupons'].includes(p.program_type));
|
||||
}
|
||||
});
|
||||
|
||||
Registries.Component.add(PromoCodeButton);
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import PosComponent from 'point_of_sale.PosComponent';
|
||||
import ProductScreen from 'point_of_sale.ProductScreen';
|
||||
import Registries from 'point_of_sale.Registries';
|
||||
import { useListener } from "@web/core/utils/hooks";
|
||||
|
||||
export class ResetProgramsButton extends PosComponent {
|
||||
setup() {
|
||||
super.setup();
|
||||
useListener('click', this.onClick);
|
||||
}
|
||||
|
||||
async onClick() {
|
||||
this.env.pos.get_order()._resetPrograms();
|
||||
}
|
||||
}
|
||||
|
||||
ResetProgramsButton.template = 'ResetProgramsButton';
|
||||
|
||||
ProductScreen.addControlButton({
|
||||
component: ResetProgramsButton,
|
||||
condition: function () {
|
||||
return this.env.pos.programs.some(p => ['coupons', 'promotion'].includes(p.program_type));
|
||||
}
|
||||
});
|
||||
|
||||
Registries.Component.add(ResetProgramsButton);
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { Gui } from 'point_of_sale.Gui';
|
||||
import PosComponent from 'point_of_sale.PosComponent';
|
||||
import ProductScreen from 'point_of_sale.ProductScreen';
|
||||
import Registries from 'point_of_sale.Registries';
|
||||
import { useListener } from "@web/core/utils/hooks";
|
||||
|
||||
export class RewardButton extends PosComponent {
|
||||
setup() {
|
||||
super.setup()
|
||||
useListener('click', this.onClick);
|
||||
}
|
||||
|
||||
/**
|
||||
* If rewards are the same, prioritize the one from freeProductRewards.
|
||||
* Make sure that the reward is claimable first.
|
||||
*/
|
||||
_mergeFreeProductRewards(freeProductRewards, potentialFreeProductRewards) {
|
||||
const result = []
|
||||
for (const reward of potentialFreeProductRewards) {
|
||||
if (!freeProductRewards.find(item => item.reward.id === reward.reward.id)) {
|
||||
result.push(reward);
|
||||
}
|
||||
}
|
||||
return freeProductRewards.concat(result);
|
||||
}
|
||||
|
||||
_getPotentialRewards() {
|
||||
const order = this.env.pos.get_order();
|
||||
// Claimable rewards excluding those from eWallet programs.
|
||||
// eWallet rewards are handled in the eWalletButton.
|
||||
let rewards = [];
|
||||
if (order) {
|
||||
const claimableRewards = order.getClaimableRewards();
|
||||
rewards = claimableRewards.filter(({ reward }) => reward.program_id.program_type !== 'ewallet');
|
||||
}
|
||||
const discountRewards = rewards.filter(({ reward }) => reward.reward_type == 'discount');
|
||||
const freeProductRewards = rewards.filter(({ reward }) => reward.reward_type == 'product');
|
||||
const potentialFreeProductRewards = order.getPotentialFreeProductRewards();
|
||||
return discountRewards.concat(this._mergeFreeProductRewards(freeProductRewards, potentialFreeProductRewards));
|
||||
}
|
||||
|
||||
hasClaimableRewards() {
|
||||
return this._getPotentialRewards().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the reward on the current order, if multiple products can be claimed opens a popup asking for which one.
|
||||
*
|
||||
* @param {Object} reward
|
||||
* @param {Integer} coupon_id
|
||||
*/
|
||||
async _applyReward(reward, coupon_id, potentialQty) {
|
||||
const order = this.env.pos.get_order();
|
||||
order.disabledRewards.delete(reward.id);
|
||||
|
||||
const args = {};
|
||||
if (reward.reward_type === 'product' && reward.multi_product) {
|
||||
const productsList = reward.reward_product_ids.map((product_id) => ({
|
||||
id: product_id,
|
||||
label: this.env.pos.db.get_product_by_id(product_id).display_name,
|
||||
item: product_id,
|
||||
}));
|
||||
const { confirmed, payload: selectedProduct } = await this.showPopup('SelectionPopup', {
|
||||
title: this.env._t('Please select a product for this reward'),
|
||||
list: productsList,
|
||||
});
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
args['product'] = selectedProduct;
|
||||
}
|
||||
if (
|
||||
(reward.reward_type == 'product' && reward.program_id.applies_on !== 'both') ||
|
||||
(reward.program_id.applies_on == 'both' && potentialQty)
|
||||
) {
|
||||
const product = this.env.pos.db.get_product_by_id(args['product'] || reward.reward_product_ids[0]);
|
||||
this.trigger(
|
||||
'click-product',
|
||||
{ product, quantity: potentialQty }
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
const result = order._applyReward(reward, coupon_id, args);
|
||||
if (result !== true) {
|
||||
// Returned an error
|
||||
Gui.showNotification(result);
|
||||
}
|
||||
order._updateRewards();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
async onClick() {
|
||||
const rewards = this._getPotentialRewards();
|
||||
if (rewards.length === 0) {
|
||||
await this.showPopup('ErrorPopup', {
|
||||
title: this.env._t('No rewards available.'),
|
||||
body: this.env._t('There are no rewards claimable for this customer.')
|
||||
});
|
||||
return false;
|
||||
} else if (rewards.length === 1) {
|
||||
return this._applyReward(rewards[0].reward, rewards[0].coupon_id, rewards[0].potentialQty);
|
||||
} else {
|
||||
const rewardsList = rewards.map((reward) => ({
|
||||
id: reward.reward.id,
|
||||
label: reward.reward.description,
|
||||
item: reward,
|
||||
}));
|
||||
const { confirmed, payload: selectedReward } = await this.showPopup('SelectionPopup', {
|
||||
title: this.env._t('Please select a reward'),
|
||||
list: rewardsList,
|
||||
});
|
||||
if (confirmed) {
|
||||
return this._applyReward(selectedReward.reward, selectedReward.coupon_id, selectedReward.potentialQty);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RewardButton.template = 'RewardButton';
|
||||
|
||||
ProductScreen.addControlButton({
|
||||
component: RewardButton,
|
||||
condition: function() {
|
||||
return this.env.pos.programs.length > 0;
|
||||
}
|
||||
});
|
||||
|
||||
Registries.Component.add(RewardButton);
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import PosComponent from 'point_of_sale.PosComponent';
|
||||
import ProductScreen from 'point_of_sale.ProductScreen';
|
||||
import Registries from 'point_of_sale.Registries';
|
||||
|
||||
export class eWalletButton extends PosComponent {
|
||||
_getEWalletRewards(order) {
|
||||
const claimableRewards = order.getClaimableRewards();
|
||||
return claimableRewards.filter((reward_line) => {
|
||||
const coupon = this.env.pos.couponCache[reward_line.coupon_id];
|
||||
return coupon && reward_line.reward.program_id.program_type == 'ewallet' && !coupon.isExpired();
|
||||
});
|
||||
}
|
||||
_getEWalletPrograms() {
|
||||
return this.env.pos.programs.filter((p) => p.program_type == 'ewallet');
|
||||
}
|
||||
async _onClickWalletButton() {
|
||||
const order = this.env.pos.get_order();
|
||||
const eWalletPrograms = this.env.pos.programs.filter((p) => p.program_type == 'ewallet');
|
||||
const orderTotal = order.get_total_with_tax();
|
||||
const eWalletRewards = this._getEWalletRewards(order);
|
||||
if (eWalletRewards.length === 0 && orderTotal >= 0) {
|
||||
this.showPopup('ErrorPopup', {
|
||||
title: this.env._t('No valid eWallet found'),
|
||||
body: this.env._t('You either have not created an eWallet or all your eWallets have expired.'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (orderTotal < 0 && eWalletPrograms.length >= 1) {
|
||||
let selectedProgram = null;
|
||||
if (eWalletPrograms.length == 1) {
|
||||
selectedProgram = eWalletPrograms[0];
|
||||
} else {
|
||||
const { confirmed, payload } = await this.showPopup('SelectionPopup', {
|
||||
title: this.env._t('Refund with eWallet'),
|
||||
list: eWalletPrograms.map((program) => ({
|
||||
id: program.id,
|
||||
item: program,
|
||||
label: program.name,
|
||||
})),
|
||||
});
|
||||
if (confirmed) {
|
||||
selectedProgram = payload;
|
||||
}
|
||||
}
|
||||
if (selectedProgram) {
|
||||
const eWalletProduct = this.env.pos.db.get_product_by_id(selectedProgram.trigger_product_ids[0]);
|
||||
order.add_product(eWalletProduct, {
|
||||
price: -orderTotal,
|
||||
merge: false,
|
||||
eWalletGiftCardProgram: selectedProgram,
|
||||
});
|
||||
}
|
||||
} else if (eWalletRewards.length >= 1) {
|
||||
let eWalletReward = null;
|
||||
if (eWalletRewards.length == 1) {
|
||||
eWalletReward = eWalletRewards[0];
|
||||
} else {
|
||||
const { confirmed, payload } = await this.showPopup('SelectionPopup', {
|
||||
title: this.env._t('Use eWallet to pay'),
|
||||
list: eWalletRewards.map(({ reward, coupon_id }) => ({
|
||||
id: reward.id,
|
||||
item: { reward, coupon_id },
|
||||
label: `${reward.description} (${reward.program_id.name})`,
|
||||
})),
|
||||
});
|
||||
if (confirmed) {
|
||||
eWalletReward = payload;
|
||||
}
|
||||
}
|
||||
if (eWalletReward) {
|
||||
const result = order._applyReward(eWalletReward.reward, eWalletReward.coupon_id, {});
|
||||
if (result !== true) {
|
||||
// Returned an error
|
||||
this.showPopup('ErrorPopup', {
|
||||
title: this.env._t('Error'),
|
||||
body: result,
|
||||
});
|
||||
}
|
||||
order._updateRewards();
|
||||
}
|
||||
}
|
||||
}
|
||||
_shouldBeHighlighted(orderTotal, eWalletPrograms, eWalletRewards) {
|
||||
return (orderTotal < 0 && eWalletPrograms.length >= 1) || eWalletRewards.length >= 1;
|
||||
}
|
||||
_getText(orderTotal, eWalletPrograms, eWalletRewards) {
|
||||
if (orderTotal < 0 && eWalletPrograms.length >= 1) {
|
||||
return this.env._t('eWallet Refund');
|
||||
} else if (eWalletRewards.length >= 1) {
|
||||
return this.env._t('eWallet Pay');
|
||||
} else {
|
||||
return this.env._t('eWallet');
|
||||
}
|
||||
}
|
||||
}
|
||||
eWalletButton.template = 'point_of_sale.eWalletButton';
|
||||
|
||||
ProductScreen.addControlButton({
|
||||
component: eWalletButton,
|
||||
condition: function () {
|
||||
return this.env.pos.programs.filter((p) => p.program_type == 'ewallet').length > 0;
|
||||
},
|
||||
});
|
||||
|
||||
Registries.Component.add(eWalletButton);
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import OrderSummary from 'point_of_sale.OrderSummary';
|
||||
import Registries from 'point_of_sale.Registries';
|
||||
|
||||
export const PosLoyaltyOrderSummary = (OrderSummary) =>
|
||||
class PosLoyaltyOrderSummary extends OrderSummary {
|
||||
getLoyaltyPoints() {
|
||||
const order = this.env.pos.get_order();
|
||||
return order.getLoyaltyPoints();
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(OrderSummary, PosLoyaltyOrderSummary)
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import Orderline from 'point_of_sale.Orderline';
|
||||
import Registries from 'point_of_sale.Registries';
|
||||
|
||||
export const PosLoyaltyOrderline = (Orderline) =>
|
||||
class extends Orderline{
|
||||
get addedClasses() {
|
||||
return Object.assign({'program-reward': this.props.line.is_reward_line}, super.addedClasses);
|
||||
}
|
||||
_isGiftCardOrEWalletReward() {
|
||||
const coupon = this.env.pos.couponCache[this.props.line.coupon_id];
|
||||
if (coupon) {
|
||||
const program = this.env.pos.program_by_id[coupon.program_id]
|
||||
return ['ewallet', 'gift_card'].includes(program.program_type) && this.props.line.is_reward_line;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
_getGiftCardOrEWalletBalance() {
|
||||
const coupon = this.env.pos.couponCache[this.props.line.coupon_id];
|
||||
if (coupon) {
|
||||
return this.env.pos.format_currency(coupon.balance);
|
||||
}
|
||||
return this.env.pos.format_currency(0);
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(Orderline, PosLoyaltyOrderline);
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
odoo.define('pos_loyalty.PartnerLine', function (require) {
|
||||
'use strict';
|
||||
|
||||
const PartnerLine = require('point_of_sale.PartnerLine');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const PosLoyaltyPartnerLine = (PartnerLine) =>
|
||||
class extends PartnerLine {
|
||||
_getLoyaltyPointsRepr(loyaltyCard) {
|
||||
const program = this.env.pos.program_by_id[loyaltyCard.program_id];
|
||||
if (program.program_type === 'ewallet') {
|
||||
return `${program.name}: ${this.env.pos.format_currency(loyaltyCard.balance)}`;
|
||||
}
|
||||
const balanceRepr = this.env.pos.format_pr(loyaltyCard.balance, 0.01);
|
||||
if (program.portal_visible) {
|
||||
return `${balanceRepr} ${program.portal_point_name}`;
|
||||
}
|
||||
return _.str.sprintf(this.env._t('%s Points'), balanceRepr);
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(PartnerLine, PosLoyaltyPartnerLine);
|
||||
|
||||
return PartnerLine;
|
||||
});
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
odoo.define('pos_loyalty.PartnerListScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const PartnerListScreen = require('point_of_sale.PartnerListScreen');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const PosLoyaltyPartnerListScreen = (PartnerListScreen) =>
|
||||
class extends PartnerListScreen {
|
||||
/**
|
||||
* Needs to be set to true to show the loyalty points in the partner list.
|
||||
* @override
|
||||
*/
|
||||
get isBalanceDisplayed() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(PartnerListScreen, PosLoyaltyPartnerListScreen);
|
||||
|
||||
return PartnerListScreen;
|
||||
});
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import PaymentScreen from 'point_of_sale.PaymentScreen';
|
||||
import Registries from 'point_of_sale.Registries';
|
||||
import session from 'web.session';
|
||||
import { PosLoyaltyCard } from '@pos_loyalty/js/Loyalty';
|
||||
|
||||
export const PosLoyaltyPaymentScreen = (PaymentScreen) =>
|
||||
class extends PaymentScreen {
|
||||
//@override
|
||||
async validateOrder(isForceValidate) {
|
||||
const pointChanges = {};
|
||||
const newCodes = [];
|
||||
for (const pe of Object.values(this.currentOrder.couponPointChanges)) {
|
||||
if (pe.coupon_id > 0) {
|
||||
pointChanges[pe.coupon_id] = pe.points;
|
||||
} else if (pe.barcode && !pe.giftCardId) {
|
||||
// New coupon with a specific code, validate that it does not exist
|
||||
newCodes.push(pe.barcode);
|
||||
}
|
||||
}
|
||||
for (const line of this.currentOrder._get_reward_lines()) {
|
||||
if (line.coupon_id < 1) {
|
||||
continue;
|
||||
}
|
||||
if (!pointChanges[line.coupon_id]) {
|
||||
pointChanges[line.coupon_id] = -line.points_cost;
|
||||
} else {
|
||||
pointChanges[line.coupon_id] -= line.points_cost;
|
||||
}
|
||||
}
|
||||
if (!await this._isOrderValid(isForceValidate)) {
|
||||
return;
|
||||
}
|
||||
// No need to do an rpc if no existing coupon is being used.
|
||||
if (!_.isEmpty(pointChanges) || newCodes.length) {
|
||||
try {
|
||||
const {successful, payload} = await this.rpc({
|
||||
model: 'pos.order',
|
||||
method: 'validate_coupon_programs',
|
||||
args: [[], pointChanges, newCodes],
|
||||
kwargs: { context: session.user_context },
|
||||
});
|
||||
// Payload may contain the points of the concerned coupons to be updated in case of error. (So that rewards can be corrected)
|
||||
if (payload && payload.updated_points) {
|
||||
for (const pointChange of Object.entries(payload.updated_points)) {
|
||||
if (this.env.pos.couponCache[pointChange[0]]) {
|
||||
this.env.pos.couponCache[pointChange[0]].balance = pointChange[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (payload && payload.removed_coupons) {
|
||||
for (const couponId of payload.removed_coupons) {
|
||||
if (this.env.pos.couponCache[couponId]) {
|
||||
delete this.env.pos.couponCache[couponId];
|
||||
}
|
||||
}
|
||||
this.currentOrder.codeActivatedCoupons = this.currentOrder.codeActivatedCoupons.filter((coupon) => !payload.removed_coupons.includes(coupon.id));
|
||||
}
|
||||
if (!successful) {
|
||||
this.showPopup('ErrorPopup', {
|
||||
title: this.env._t('Error validating rewards'),
|
||||
body: payload.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (_e) {
|
||||
// Do nothing with error, while this validation step is nice for error messages
|
||||
// it should not be blocking.
|
||||
}
|
||||
}
|
||||
await super.validateOrder(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async _postPushOrderResolve(order, server_ids) {
|
||||
// Compile data for our function
|
||||
const rewardLines = order._get_reward_lines();
|
||||
const partner = order.get_partner();
|
||||
let couponData = Object.values(order.couponPointChanges).reduce((agg, pe) => {
|
||||
agg[pe.coupon_id] = Object.assign({}, pe, {
|
||||
points: pe.points - order._getPointsCorrection(this.env.pos.program_by_id[pe.program_id]),
|
||||
});
|
||||
const program = this.env.pos.program_by_id[pe.program_id];
|
||||
if (program.is_nominative && partner) {
|
||||
agg[pe.coupon_id].partner_id = partner.id;
|
||||
}
|
||||
if (program.program_type != 'loyalty') {
|
||||
agg[pe.coupon_id].date_to = program.date_to;
|
||||
}
|
||||
return agg;
|
||||
}, {});
|
||||
for (const line of rewardLines) {
|
||||
const reward = this.env.pos.reward_by_id[line.reward_id];
|
||||
if (!couponData[line.coupon_id]) {
|
||||
couponData[line.coupon_id] = {
|
||||
points: 0,
|
||||
program_id: reward.program_id.id,
|
||||
coupon_id: line.coupon_id,
|
||||
barcode: false,
|
||||
}
|
||||
if (reward.program_type != 'loyalty') {
|
||||
couponData[line.coupon_id].date_to = reward.program_id.date_to;
|
||||
}
|
||||
}
|
||||
if (!couponData[line.coupon_id].line_codes) {
|
||||
couponData[line.coupon_id].line_codes = [];
|
||||
}
|
||||
if (!couponData[line.coupon_id].line_codes.includes(line.reward_identifier_code)) {
|
||||
!couponData[line.coupon_id].line_codes.push(line.reward_identifier_code);
|
||||
}
|
||||
couponData[line.coupon_id].points -= line.points_cost;
|
||||
}
|
||||
// We actually do not care about coupons for 'current' programs that did not claim any reward, they will be lost if not validated
|
||||
couponData = Object.fromEntries(Object.entries(couponData).filter(([key, value]) => {
|
||||
const program = this.env.pos.program_by_id[value.program_id];
|
||||
if (program.applies_on === 'current') {
|
||||
return value.line_codes && value.line_codes.length;
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
if (!_.isEmpty(couponData)) {
|
||||
const payload = await this.rpc({
|
||||
model: 'pos.order',
|
||||
method: 'confirm_coupon_programs',
|
||||
args: [server_ids, couponData],
|
||||
kwargs: { context: session.user_context },
|
||||
});
|
||||
if (payload.coupon_updates) {
|
||||
for (const couponUpdate of payload.coupon_updates) {
|
||||
let dbCoupon = this.env.pos.couponCache[couponUpdate.old_id];
|
||||
if (dbCoupon) {
|
||||
dbCoupon.id = couponUpdate.id;
|
||||
dbCoupon.balance = couponUpdate.points;
|
||||
dbCoupon.code = couponUpdate.code;
|
||||
} else {
|
||||
dbCoupon = new PosLoyaltyCard(
|
||||
couponUpdate.code, couponUpdate.id, couponUpdate.program_id, couponUpdate.partner_id, couponUpdate.points);
|
||||
this.env.pos.partnerId2CouponIds[partner.id] = this.env.pos.partnerId2CouponIds[partner.id] || new Set();
|
||||
this.env.pos.partnerId2CouponIds[partner.id].add(couponUpdate.id);
|
||||
}
|
||||
delete this.env.pos.couponCache[couponUpdate.old_id];
|
||||
this.env.pos.couponCache[couponUpdate.id] = dbCoupon;
|
||||
}
|
||||
}
|
||||
// Update the usage count since it is checked based on local data
|
||||
if (payload.program_updates) {
|
||||
for (const programUpdate of payload.program_updates) {
|
||||
const program = this.env.pos.program_by_id[programUpdate.program_id];
|
||||
if (program) {
|
||||
program.total_order_count = programUpdate.usages;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (payload.coupon_report) {
|
||||
for (const report_entry of Object.entries(payload.coupon_report)) {
|
||||
await this.env.legacyActionManager.do_action(report_entry[0], {
|
||||
additional_context: {
|
||||
active_ids: report_entry[1],
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
order.new_coupon_info = payload.new_coupon_info;
|
||||
}
|
||||
return super._postPushOrderResolve(order, server_ids);
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(PaymentScreen, PosLoyaltyPaymentScreen);
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import ProductScreen from 'point_of_sale.ProductScreen';
|
||||
import Registries from 'point_of_sale.Registries';
|
||||
import { useBarcodeReader } from 'point_of_sale.custom_hooks';
|
||||
|
||||
export const PosLoyaltyProductScreen = (ProductScreen) =>
|
||||
class extends ProductScreen {
|
||||
setup() {
|
||||
super.setup();
|
||||
useBarcodeReader({
|
||||
coupon: this._onCouponScan,
|
||||
});
|
||||
}
|
||||
async _onClickPay() {
|
||||
const order = this.env.pos.get_order();
|
||||
const eWalletLine = order.get_orderlines().find(line => line.getEWalletGiftCardProgramType() === 'ewallet');
|
||||
if (eWalletLine && !order.get_partner()) {
|
||||
const {confirmed} = await this.showPopup('ConfirmPopup', {
|
||||
title: this.env._t('Customer needed'),
|
||||
body: this.env._t('eWallet requires a customer to be selected'),
|
||||
});
|
||||
if (confirmed) {
|
||||
const { confirmed, payload: newPartner } = await this.showTempScreen(
|
||||
'PartnerListScreen',
|
||||
{ partner: null }
|
||||
);
|
||||
if (confirmed) {
|
||||
order.set_partner(newPartner);
|
||||
order.updatePricelist(newPartner);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return super._onClickPay(...arguments);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets up the options for the gift card product.
|
||||
* @param {object} program
|
||||
* @param {object} options
|
||||
* @returns {Promise<boolean>} whether to proceed with adding the product or not
|
||||
*/
|
||||
async _setupGiftCardOptions(program, options) {
|
||||
options.quantity = 1;
|
||||
options.merge = false;
|
||||
options.eWalletGiftCardProgram = program;
|
||||
|
||||
// If gift card program setting is 'scan_use', ask for the code.
|
||||
if (this.env.pos.config.gift_card_settings == 'scan_use') {
|
||||
const { confirmed, payload: code } = await this.showPopup('TextInputPopup', {
|
||||
title: this.env._t('Generate a Gift Card'),
|
||||
startingValue: '',
|
||||
placeholder: this.env._t('Enter the gift card code'),
|
||||
});
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
const trimmedCode = code.trim();
|
||||
let nomenclatureRules = this.env.barcode_reader.barcode_parser.nomenclature.rules;
|
||||
if (this.env.barcode_reader.fallbackBarcodeParser) {
|
||||
nomenclatureRules.push(...this.env.barcode_reader.fallbackBarcodeParser.nomenclature.rules);
|
||||
}
|
||||
const couponNomenclatureRules = _.filter(nomenclatureRules, function(rule) {
|
||||
return rule.type == "coupon";
|
||||
});
|
||||
let nomenclatureCodePatterns = [];
|
||||
_.each(_.pluck(couponNomenclatureRules, "pattern"), function(pattern){
|
||||
nomenclatureCodePatterns.push(...pattern.split("|"));
|
||||
});
|
||||
const trimmedCodeValid = _.find(nomenclatureCodePatterns, function(pattern) {
|
||||
return trimmedCode.startsWith(pattern);
|
||||
});
|
||||
if (trimmedCode && trimmedCodeValid) {
|
||||
// check if the code exist in the database
|
||||
// if so, use its balance, otherwise, use the unit price of the gift card product
|
||||
const fetchedGiftCard = await this.rpc({
|
||||
model: 'loyalty.card',
|
||||
method: 'search_read',
|
||||
args: [
|
||||
[['code', '=', trimmedCode], ['program_id', '=', program.id]],
|
||||
['points', 'source_pos_order_id'],
|
||||
],
|
||||
});
|
||||
// There should be maximum one gift card for a given code.
|
||||
const giftCard = fetchedGiftCard[0];
|
||||
if (giftCard && giftCard.source_pos_order_id) {
|
||||
this.showPopup('ErrorPopup', {
|
||||
title: this.env._t('This gift card has already been sold'),
|
||||
body: this.env._t('You cannot sell a gift card that has already been sold.'),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
options.giftBarcode = trimmedCode;
|
||||
if (giftCard) {
|
||||
// Use the balance of the gift card as the price of the orderline.
|
||||
// NOTE: No need to convert the points to price because when opening a session,
|
||||
// the gift card programs are made sure to have 1 point = 1 currency unit.
|
||||
options.price = giftCard.points;
|
||||
options.giftCardId = giftCard.id;
|
||||
}
|
||||
} else {
|
||||
this.showNotification('Please enter a valid gift card code.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async setupEWalletOptions(program, options) {
|
||||
options.quantity = 1;
|
||||
options.merge = false;
|
||||
options.eWalletGiftCardProgram = program;
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* If the product is a potential reward, also apply the reward.
|
||||
* @override
|
||||
*/
|
||||
async _addProduct(product, options) {
|
||||
const linkedProgramIds = this.env.pos.productId2ProgramIds[product.id] || [];
|
||||
const linkedPrograms = linkedProgramIds.map(id => this.env.pos.program_by_id[id]);
|
||||
let selectedProgram = null;
|
||||
if (linkedPrograms.length > 1) {
|
||||
const { confirmed, payload: program } = await this.showPopup('SelectionPopup', {
|
||||
title: this.env._t('Select program'),
|
||||
list: linkedPrograms.map((program) => ({
|
||||
id: program.id,
|
||||
item: program,
|
||||
label: program.name,
|
||||
})),
|
||||
});
|
||||
if (confirmed) {
|
||||
selectedProgram = program;
|
||||
} else {
|
||||
// Do nothing here if the selection is cancelled.
|
||||
return;
|
||||
}
|
||||
} else if (linkedPrograms.length === 1) {
|
||||
selectedProgram = linkedPrograms[0];
|
||||
}
|
||||
if (selectedProgram && selectedProgram.program_type == 'gift_card') {
|
||||
const shouldProceed = await this._setupGiftCardOptions(selectedProgram, options);
|
||||
if (!shouldProceed) {
|
||||
return;
|
||||
}
|
||||
} else if (selectedProgram && selectedProgram.program_type == 'ewallet') {
|
||||
const shouldProceed = await this.setupEWalletOptions(selectedProgram, options);
|
||||
if (!shouldProceed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const order = this.env.pos.get_order();
|
||||
const potentialRewards = order.getPotentialFreeProductRewards();
|
||||
let rewardsToApply = [];
|
||||
for (const reward of potentialRewards) {
|
||||
for (const reward_product_id of reward.reward.reward_product_ids) {
|
||||
if (reward_product_id == product.id) {
|
||||
rewardsToApply.push(reward);
|
||||
}
|
||||
}
|
||||
}
|
||||
await super._addProduct(product, options);
|
||||
await order._updatePrograms();
|
||||
if (rewardsToApply.length == 1) {
|
||||
const reward = rewardsToApply[0];
|
||||
order._applyReward(reward.reward, reward.coupon_id, { product: product.id });
|
||||
}
|
||||
}
|
||||
|
||||
_onCouponScan(code) {
|
||||
// IMPROVEMENT: Ability to understand if the scanned code is to be paid or to be redeemed.
|
||||
this.currentOrder.activateCode(code.base_code);
|
||||
}
|
||||
|
||||
async _updateSelectedOrderline(event) {
|
||||
const selectedLine = this.currentOrder.get_selected_orderline();
|
||||
if (event.detail.key === '-') {
|
||||
if (selectedLine && selectedLine.eWalletGiftCardProgram) {
|
||||
// Do not allow negative quantity or price in a gift card or ewallet orderline.
|
||||
// Refunding gift card or ewallet is not supported.
|
||||
this.showNotification(this.env._t('You cannot set negative quantity or price to gift card or ewallet.'), 4000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (selectedLine && selectedLine.is_reward_line && !selectedLine.manual_reward &&
|
||||
(event.detail.key === 'Backspace' || event.detail.key === 'Delete')) {
|
||||
const reward = this.env.pos.reward_by_id[selectedLine.reward_id];
|
||||
const { confirmed } = await this.showPopup('ConfirmPopup', {
|
||||
title: this.env._t('Deactivating reward'),
|
||||
body: _.str.sprintf(
|
||||
this.env._t('Are you sure you want to remove %s from this order?\n You will still be able to claim it through the reward button.'),
|
||||
reward.description
|
||||
),
|
||||
cancelText: this.env._t('No'),
|
||||
confirmText: this.env._t('Yes'),
|
||||
});
|
||||
if (confirmed) {
|
||||
event.detail.buffer = null;
|
||||
} else {
|
||||
// Cancel backspace
|
||||
return;
|
||||
}
|
||||
}
|
||||
return super._updateSelectedOrderline(...arguments);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1/ Perform the usual set value operation (super._setValue) if the line being modified
|
||||
* is not a reward line or if it is a reward line, the `val` being set is '' or 'remove' only.
|
||||
*
|
||||
* 2/ Update activated programs and coupons when removing a reward line.
|
||||
*
|
||||
* 3/ Trigger 'update-rewards' if the line being modified is a regular line or
|
||||
* if removing a reward line.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_setValue(val) {
|
||||
const selectedLine = this.currentOrder.get_selected_orderline();
|
||||
if (
|
||||
!selectedLine ||
|
||||
!selectedLine.is_reward_line ||
|
||||
(selectedLine.is_reward_line && ['', 'remove'].includes(val))
|
||||
) {
|
||||
super._setValue(val);
|
||||
}
|
||||
if (!selectedLine) return;
|
||||
if (selectedLine.is_reward_line && val === 'remove') {
|
||||
this.currentOrder.disabledRewards.add(selectedLine.reward_id);
|
||||
const coupon = this.env.pos.couponCache[selectedLine.coupon_id];
|
||||
if (coupon && coupon.id > 0 && this.currentOrder.codeActivatedCoupons.find((c) => c.code === coupon.code)) {
|
||||
delete this.env.pos.couponCache[selectedLine.coupon_id];
|
||||
this.currentOrder.codeActivatedCoupons.splice(this.currentOrder.codeActivatedCoupons.findIndex((coupon) => {
|
||||
return coupon.id === selectedLine.coupon_id;
|
||||
}), 1);
|
||||
}
|
||||
}
|
||||
if (!selectedLine.is_reward_line || (selectedLine.is_reward_line && val === 'remove')) {
|
||||
selectedLine.order._updateRewards();
|
||||
}
|
||||
}
|
||||
async _showDecreaseQuantityPopup() {
|
||||
const result = await super._showDecreaseQuantityPopup();
|
||||
if (result){
|
||||
this.env.pos.get_order()._updateRewards();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(ProductScreen, PosLoyaltyProductScreen);
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import TicketScreen from 'point_of_sale.TicketScreen';
|
||||
import Registries from 'point_of_sale.Registries';
|
||||
import NumberBuffer from 'point_of_sale.NumberBuffer';
|
||||
|
||||
/**
|
||||
* Prevent refunding ewallet/gift card lines.
|
||||
*/
|
||||
export const PosLoyaltyTicketScreen = (TicketScreen) =>
|
||||
class PosLoyaltyTicketScreen extends TicketScreen {
|
||||
_onUpdateSelectedOrderline() {
|
||||
const order = this.getSelectedSyncedOrder();
|
||||
if (!order) return NumberBuffer.reset();
|
||||
const selectedOrderlineId = this.getSelectedOrderlineId();
|
||||
const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId);
|
||||
if (orderline && this._isEWalletGiftCard(orderline)) {
|
||||
this._showNotAllowedRefundNotification();
|
||||
return NumberBuffer.reset();
|
||||
}
|
||||
return super._onUpdateSelectedOrderline(...arguments);
|
||||
}
|
||||
_prepareAutoRefundOnOrder(order) {
|
||||
const selectedOrderlineId = this.getSelectedOrderlineId();
|
||||
const orderline = order.orderlines.find((line) => line.id == selectedOrderlineId);
|
||||
if (this._isEWalletGiftCard(orderline)) {
|
||||
this._showNotAllowedRefundNotification();
|
||||
return false;
|
||||
}
|
||||
return super._prepareAutoRefundOnOrder(...arguments);
|
||||
}
|
||||
_showNotAllowedRefundNotification() {
|
||||
this.showNotification(this.env._t("Refunding a top up or reward product for an eWallet or gift card program is not allowed."), 5000);
|
||||
}
|
||||
_isEWalletGiftCard(orderline) {
|
||||
const linkedProgramIds = this.env.pos.productId2ProgramIds[orderline.product.id];
|
||||
if (linkedProgramIds) {
|
||||
return linkedProgramIds.length > 0;
|
||||
}
|
||||
if (orderline.is_reward_line) {
|
||||
const reward = this.env.pos.reward_by_id[orderline.reward_id];
|
||||
const program = reward && reward.program_id;
|
||||
if (program && ['gift_card', 'ewallet'].includes(program.program_type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(TicketScreen, PosLoyaltyTicketScreen);
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<templates>
|
||||
<t t-inherit="loyalty.portal_loyalty_card_dialog" t-inherit-mode="extension">
|
||||
<div name="history_lines" position="before">
|
||||
<div
|
||||
t-if="props.program.program_type == 'loyalty'"
|
||||
class="d-flex align-items-center flex-column"
|
||||
>
|
||||
<img
|
||||
t-att-src="`/report/barcode/Code128/${props.card.code}?&width=350&height=100`"
|
||||
style="width:400px;height:100px"
|
||||
/>
|
||||
<span t-out="props.card.code" class="fs-5"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { ErrorPopup } from 'point_of_sale.tour.ErrorPopupTourMethods';
|
||||
import { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';
|
||||
import { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';
|
||||
import { TicketScreen } from 'point_of_sale.tour.TicketScreenTourMethods';
|
||||
import { Chrome } from 'point_of_sale.tour.ChromeTourMethods';
|
||||
import { PartnerListScreen } from 'point_of_sale.tour.PartnerListScreenTourMethods';
|
||||
import { getSteps, startSteps } from 'point_of_sale.tour.utils';
|
||||
import Tour from 'web_tour.tour';
|
||||
|
||||
//#region EWalletProgramTour1
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Topup 50$ for partner_aaa
|
||||
ProductScreen.do.clickDisplayedProduct('Top-up eWallet');
|
||||
PosLoyalty.check.orderTotalIs('50.00');
|
||||
ProductScreen.do.clickPayButton(false);
|
||||
// If there's no partner, we asked to redirect to the partner list screen.
|
||||
Chrome.do.confirmPopup();
|
||||
PartnerListScreen.check.isShown();
|
||||
PartnerListScreen.do.clickPartner('AAAAAAA');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Topup 10$ for partner_bbb
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('BBBBBBB');
|
||||
ProductScreen.exec.addOrderline('Top-up eWallet', '1', '10');
|
||||
PosLoyalty.check.orderTotalIs('10.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('EWalletProgramTour1', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region EWalletProgramTour2
|
||||
|
||||
const getEWalletText = (suffix) => 'eWallet' + (suffix !== '' ? ` ${suffix}` : '');
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '2', '6', '12.00');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: false });
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAAAAAA');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });
|
||||
PosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Consume partner_bbb's full eWallet.
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('BBBBBBB');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: false });
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '6', '6', '36.00');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });
|
||||
PosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));
|
||||
PosLoyalty.check.orderTotalIs('26.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Switching partners should work.
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('BBBBBBB');
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '2', '19', '38.00');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: false });
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAAAAAA');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });
|
||||
PosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('BBBBBBB');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: false });
|
||||
PosLoyalty.check.orderTotalIs('38.00');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAAAAAA');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });
|
||||
PosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Refund with eWallet.
|
||||
// - Make an order to refund.
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('BBBBBBB');
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '1', '20', '20.00');
|
||||
PosLoyalty.check.orderTotalIs('20.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
// - Refund order.
|
||||
ProductScreen.do.clickRefund();
|
||||
TicketScreen.check.filterIs('Paid');
|
||||
TicketScreen.do.selectOrder('-0004');
|
||||
TicketScreen.check.partnerIs('BBBBBBB');
|
||||
TicketScreen.do.confirmRefund();
|
||||
ProductScreen.check.isShown();
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Refund') });
|
||||
PosLoyalty.do.clickEWalletButton(getEWalletText('Refund'));
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('EWalletProgramTour2', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region ExpiredEWalletProgramTour
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAAA');
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '2', '6', '12.00');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: false });
|
||||
PosLoyalty.do.clickEWalletButton();
|
||||
ErrorPopup.check.isShown();
|
||||
ErrorPopup.do.clickConfirm();
|
||||
|
||||
Tour.register('ExpiredEWalletProgramTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
//#endregion
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer("partner_a");
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: false });
|
||||
ProductScreen.exec.addOrderline("product_a", "1");
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText("Pay") });
|
||||
PosLoyalty.do.clickEWalletButton(getEWalletText("Pay"));
|
||||
PosLoyalty.check.pointsAwardedAre("100"),
|
||||
PosLoyalty.exec.finalizeOrder("Cash", "90");
|
||||
Tour.register("PosLoyaltyPointsEwallet", { test: true, url: "/pos/web" }, getSteps());
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';
|
||||
import { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';
|
||||
import { TextInputPopup } from 'point_of_sale.tour.TextInputPopupTourMethods';
|
||||
import { getSteps, startSteps } from 'point_of_sale.tour.utils';
|
||||
import Tour from 'web_tour.tour';
|
||||
|
||||
//#region GiftCardProgramCreateSetTour1
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Gift Card');
|
||||
PosLoyalty.check.orderTotalIs('50.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
Tour.register('GiftCardProgramCreateSetTour1', { test: true, url: '/pos/web' }, getSteps());
|
||||
//#endregion
|
||||
|
||||
//#region GiftCardProgramCreateSetTour2
|
||||
startSteps();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
PosLoyalty.do.enterCode('044123456');
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
Tour.register('GiftCardProgramCreateSetTour2', { test: true, url: '/pos/web' }, getSteps());
|
||||
//#endregion
|
||||
|
||||
//#region GiftCardProgramScanUseTour
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
// Pay the 5$ gift card.
|
||||
ProductScreen.do.clickDisplayedProduct('Gift Card');
|
||||
TextInputPopup.check.isShown();
|
||||
TextInputPopup.do.inputText('044123456');
|
||||
TextInputPopup.do.clickConfirm();
|
||||
PosLoyalty.check.orderTotalIs('5.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
// Partially use the gift card. (4$)
|
||||
ProductScreen.exec.addOrderline('Desk Pad', '2', '2', '4.0');
|
||||
PosLoyalty.do.enterCode('044123456');
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
// Use the remaining of the gift card. (5$ - 4$ = 1$)
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '6', '6', '36.0');
|
||||
PosLoyalty.do.enterCode('044123456');
|
||||
PosLoyalty.check.orderTotalIs('35.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
Tour.register('GiftCardProgramScanUseTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
//#endregion
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Gift Card');
|
||||
TextInputPopup.check.isShown();
|
||||
TextInputPopup.do.inputText('044123456');
|
||||
TextInputPopup.do.clickConfirm();
|
||||
PosLoyalty.check.orderTotalIs('50.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer("partner_a");
|
||||
ProductScreen.exec.addOrderline("product_a", "1");
|
||||
PosLoyalty.do.enterCode("044123456");
|
||||
PosLoyalty.check.orderTotalIs("50.00");
|
||||
PosLoyalty.check.pointsAwardedAre("100"),
|
||||
PosLoyalty.exec.finalizeOrder("Cash", "50");
|
||||
Tour.register("PosLoyaltyPointsGiftcard", { test: true, url: "/pos/web" }, getSteps());
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Gift Card');
|
||||
TextInputPopup.check.isShown();
|
||||
TextInputPopup.do.inputText('044123456');
|
||||
TextInputPopup.do.clickConfirm();
|
||||
PosLoyalty.check.orderTotalIs('50.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
ProductScreen.do.clickDisplayedProduct("Test Product A");
|
||||
PosLoyalty.do.enterCode("044123456");
|
||||
PosLoyalty.check.orderTotalIs("50.00");
|
||||
ProductScreen.check.checkTaxAmount("-6.52");
|
||||
Tour.register("PosLoyaltyGiftCardTaxes", { test: true }, getSteps());
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Gift Card');
|
||||
TextInputPopup.check.isShown();
|
||||
TextInputPopup.do.inputText('044123456');
|
||||
TextInputPopup.do.clickConfirm();
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
ProductScreen.do.pressNumpad("Price 5");
|
||||
PosLoyalty.check.orderTotalIs('5.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
Tour.register("PosLoyaltyGiftCardNoPoints", { test: true }, getSteps());
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';
|
||||
import { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';
|
||||
import { SelectionPopup } from 'point_of_sale.tour.SelectionPopupTourMethods';
|
||||
import { getSteps, startSteps } from 'point_of_sale.tour.utils';
|
||||
import Tour from 'web_tour.tour';
|
||||
|
||||
const getEWalletText = (suffix) => 'eWallet' + (suffix !== '' ? ` ${suffix}` : '');
|
||||
|
||||
startSteps();
|
||||
// One card for gift_card_1.
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Gift Card');
|
||||
SelectionPopup.check.hasSelectionItem('gift_card_1');
|
||||
SelectionPopup.check.hasSelectionItem('gift_card_2');
|
||||
SelectionPopup.do.clickItem('gift_card_1');
|
||||
ProductScreen.do.pressNumpad('Price');
|
||||
ProductScreen.do.pressNumpad('1 0');
|
||||
PosLoyalty.check.orderTotalIs('10.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
// One card for gift_card_1.
|
||||
ProductScreen.do.clickDisplayedProduct('Gift Card');
|
||||
SelectionPopup.do.clickItem('gift_card_2');
|
||||
ProductScreen.do.pressNumpad('Price');
|
||||
ProductScreen.do.pressNumpad('2 0');
|
||||
PosLoyalty.check.orderTotalIs('20.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
// Top up ewallet_1 for AAAAAAA.
|
||||
ProductScreen.do.clickDisplayedProduct('Top-up eWallet');
|
||||
SelectionPopup.check.hasSelectionItem('ewallet_1');
|
||||
SelectionPopup.check.hasSelectionItem('ewallet_2');
|
||||
SelectionPopup.do.clickItem('ewallet_1');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAAAAAA');
|
||||
ProductScreen.do.pressNumpad('Price');
|
||||
ProductScreen.do.pressNumpad('3 0');
|
||||
PosLoyalty.check.orderTotalIs('30.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
// Top up ewallet_2 for AAAAAAA.
|
||||
ProductScreen.do.clickDisplayedProduct('Top-up eWallet');
|
||||
SelectionPopup.do.clickItem('ewallet_2');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAAAAAA');
|
||||
ProductScreen.do.pressNumpad('Price');
|
||||
ProductScreen.do.pressNumpad('4 0');
|
||||
PosLoyalty.check.orderTotalIs('40.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
// Top up ewallet_1 for BBBBBBB.
|
||||
ProductScreen.do.clickDisplayedProduct('Top-up eWallet');
|
||||
SelectionPopup.do.clickItem('ewallet_1');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('BBBBBBB');
|
||||
PosLoyalty.check.orderTotalIs('50.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
// Consume 12$ from ewallet_1 of AAAAAAA.
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '2', '6', '12.00');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: false });
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAAAAAA');
|
||||
PosLoyalty.check.eWalletButtonState({ highlighted: true, text: getEWalletText('Pay') });
|
||||
PosLoyalty.do.clickEWalletButton(getEWalletText('Pay'));
|
||||
SelectionPopup.check.hasSelectionItem('ewallet_1');
|
||||
SelectionPopup.check.hasSelectionItem('ewallet_2');
|
||||
SelectionPopup.do.clickItem('ewallet_1');
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('MultipleGiftWalletProgramsTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';
|
||||
import { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';
|
||||
import { getSteps, startSteps } from 'point_of_sale.tour.utils';
|
||||
import Tour from 'web_tour.tour';
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Order1: Generates 2 points.
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '2');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner AAA');
|
||||
PosLoyalty.check.orderTotalIs('6.40');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Order2: Consumes points to get free product.
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner AAA');
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.00');
|
||||
// At this point, Test Partner AAA has 4 points.
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '3.00');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
PosLoyalty.check.orderTotalIs('6.40');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Order3: Generate 4 points.
|
||||
// - Initially gets free product, but was removed automatically by changing the
|
||||
// number of items to zero.
|
||||
// - It's impossible to checked here if the free product reward is really removed
|
||||
// so we check in the backend the number of orders that consumed the reward.
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '4');
|
||||
PosLoyalty.check.orderTotalIs('12.80');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner AAA');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
// At this point, the reward line should have been automatically removed
|
||||
// because there is not enough points to purchase it. Unfortunately, we
|
||||
// can't check that here.
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '3.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '4.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
|
||||
PosLoyalty.check.orderTotalIs('12.80');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyLoyaltyProgram1', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Order1: Immediately set the customer to Test Partner AAA which has 4 points.
|
||||
// - He has enough points to purchase a free product but since there is still
|
||||
// no product in the order, reward button should not yet be highlighted.
|
||||
// - Furthermore, clicking the reward product should not add it as reward product.
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner AAA');
|
||||
// No item in the order, so reward button is off.
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
PosLoyalty.check.orderTotalIs('3.20');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Order2: Generate 4 points for Test Partner CCC.
|
||||
// - Reference: Order2_CCC
|
||||
// - But set Test Partner BBB first as the customer.
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner BBB');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '2.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '3.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '4.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner CCC');
|
||||
PosLoyalty.check.customerIs('Test Partner CCC');
|
||||
PosLoyalty.check.orderTotalIs('12.80');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Order3: Generate 3 points for Test Partner BBB.
|
||||
// - Reference: Order3_BBB
|
||||
// - But set Test Partner CCC first as the customer.
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner CCC');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '3');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner BBB');
|
||||
PosLoyalty.check.customerIs('Test Partner BBB');
|
||||
PosLoyalty.check.orderTotalIs('9.60');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Order4: Should not have reward because the customer will be removed.
|
||||
// - Reference: Order4_no_reward
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
ProductScreen.check.selectedOrderlineHas('Whiteboard Pen', '1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner CCC');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
// This deselects the customer.
|
||||
PosLoyalty.do.unselectPartner();
|
||||
PosLoyalty.check.customerIs('Customer');
|
||||
PosLoyalty.check.orderTotalIs('6.40');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyLoyaltyProgram2', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Generates 10.2 points and use points to get the reward product with zero sale price
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '2');
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner AAA');
|
||||
|
||||
// At this point, the free_product program is triggered.
|
||||
// The reward button should be highlighted.
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '0.0', '1.00');
|
||||
|
||||
PosLoyalty.check.orderTotalIs('10.2');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyLoyaltyProgram3', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAA Partner');
|
||||
ProductScreen.exec.addOrderline('Test Product 1', '1.00', '100');
|
||||
ProductScreen.check.totalAmountIs('80.00');
|
||||
|
||||
Tour.register('PosLoyaltyPromotion', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Generates 10.2 points and use points to get the reward product with zero sale price
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '3');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyNextOrderCouponExpirationDate', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('Test Partner');
|
||||
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '1');
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '1');
|
||||
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
|
||||
PosLoyalty.check.orderTotalIs('5.10');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyDontGrantPointsForRewardOrderLines', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
|
@ -1,252 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';
|
||||
import { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';
|
||||
import { SelectionPopup } from 'point_of_sale.tour.SelectionPopupTourMethods';
|
||||
import { getSteps, startSteps } from 'point_of_sale.tour.utils';
|
||||
import Tour from 'web_tour.tour';
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '2');
|
||||
|
||||
// At this point, the free_product program is triggered.
|
||||
// The reward button should be highlighted.
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
// Since the reward button is highlighted, clicking the reward product should be added as reward.
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '3.00');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-5.10', '1.00');
|
||||
// In the succeeding 2 clicks on the product, it is considered as a regular product.
|
||||
// In the third click, the product will be added as reward.
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '6.00');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-10.20', '2.00');
|
||||
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
PosLoyalty.check.orderTotalIs('25.50');
|
||||
// Finalize order that consumed a reward.
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '1.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '2.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-5.10', '1.00');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '0.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '1.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Organizer');
|
||||
ProductScreen.check.selectedOrderlineHas('Desk Organizer', '2.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
// Finalize order but without the reward.
|
||||
// This step is important. When syncing the order, no reward should be synced.
|
||||
PosLoyalty.check.orderTotalIs('10.20');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
|
||||
ProductScreen.exec.addOrderline('Magnetic Board', '2');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.do.clickDisplayedProduct('Magnetic Board');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');
|
||||
ProductScreen.do.clickOrderline('Magnetic Board', '3.00');
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', '3.00');
|
||||
ProductScreen.do.pressNumpad('6');
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', '6.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-6.40', '2.00');
|
||||
// Finalize order that consumed a reward.
|
||||
PosLoyalty.check.orderTotalIs('11.88');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
ProductScreen.exec.addOrderline('Magnetic Board', '6');
|
||||
ProductScreen.do.clickDisplayedProduct('Whiteboard Pen');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
|
||||
ProductScreen.do.clickOrderline('Magnetic Board', '6.00');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
// At this point, the reward should have been removed.
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', '0.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Magnetic Board');
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', '1.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Magnetic Board');
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', '2.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Magnetic Board');
|
||||
ProductScreen.check.selectedOrderlineHas('Magnetic Board', '3.00');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Whiteboard Pen', '-3.20', '1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
|
||||
PosLoyalty.check.orderTotalIs('5.94');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Promotion: 2 items of shelves, get desk_pad/monitor_stand free
|
||||
// This is the 5th order.
|
||||
ProductScreen.do.clickDisplayedProduct('Wall Shelf Unit');
|
||||
ProductScreen.check.selectedOrderlineHas('Wall Shelf Unit', '1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.do.clickDisplayedProduct('Small Shelf');
|
||||
ProductScreen.check.selectedOrderlineHas('Small Shelf', '1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
// Click reward product. Should be automatically added as reward.
|
||||
ProductScreen.do.clickDisplayedProduct('Desk Pad');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
PosLoyalty.check.hasRewardLine('Free Product', '-1.98', '1.00');
|
||||
// Remove the reward line. The next steps will check if cashier
|
||||
// can select from the different reward products.
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
SelectionPopup.check.hasSelectionItem('Monitor Stand');
|
||||
SelectionPopup.check.hasSelectionItem('Desk Pad');
|
||||
SelectionPopup.do.clickItem('Desk Pad');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
PosLoyalty.check.hasRewardLine('Free Product', '-1.98', '1.00');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
ProductScreen.do.pressNumpad('Backspace');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.claimReward('Monitor Stand');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.check.selectedOrderlineHas('Monitor Stand', '1.00', '3.19');
|
||||
PosLoyalty.check.hasRewardLine('Free Product', '-3.19', '1.00');
|
||||
PosLoyalty.check.orderTotalIs('4.81');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyFreeProductTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAA Partner');
|
||||
ProductScreen.exec.addOrderline('Test Product A', '1');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Test Product A', '-11.50', '1.00');
|
||||
|
||||
Tour.register('PosLoyaltyFreeProductTour2', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product A');
|
||||
ProductScreen.check.selectedOrderlineHas('Test Product A', '1.00', '40.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product B');
|
||||
ProductScreen.check.selectedOrderlineHas('Test Product B', '1.00', '40.00');
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
SelectionPopup.do.clickItem("$ 10 per order on specific products");
|
||||
PosLoyalty.check.hasRewardLine('$ 10 per order on specific products', '-10.00', '1.00');
|
||||
PosLoyalty.check.orderTotalIs('70.00');
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
SelectionPopup.do.clickItem("$ 10 per order on specific products");
|
||||
PosLoyalty.check.orderTotalIs('60.00');
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
SelectionPopup.do.clickItem("$ 30 per order on specific products");
|
||||
PosLoyalty.check.hasRewardLine('$ 30 per order on specific products', '-30.00', '1.00');
|
||||
PosLoyalty.check.orderTotalIs('30.00');
|
||||
|
||||
Tour.register('PosLoyaltySpecificDiscountTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product A');
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product C');
|
||||
PosLoyalty.check.orderTotalIs('130.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
PosLoyalty.check.orderTotalIs('130.00');
|
||||
|
||||
Tour.register('PosLoyaltySpecificDiscountWithFreeProductTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
ProductScreen.check.selectedOrderlineHas('Product A', '1.00', '15.00');
|
||||
PosLoyalty.check.orderTotalIs('15.00');
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Product B');
|
||||
ProductScreen.check.selectedOrderlineHas('Product B', '1.00', '50.00');
|
||||
PosLoyalty.check.orderTotalIs('40.00');
|
||||
|
||||
Tour.register('PosLoyaltySpecificDiscountWithRewardProductDomainTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
ProductScreen.check.selectedOrderlineHas('Product A', '1.00', '15.00');
|
||||
PosLoyalty.check.orderTotalIs('15.00');
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Product B');
|
||||
ProductScreen.check.selectedOrderlineHas('Product B', '1.00', '50.00');
|
||||
PosLoyalty.check.orderTotalIs('40.00');
|
||||
|
||||
Tour.register('PosLoyaltySpecificDiscountCategoryTour', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct("Desk Organizer");
|
||||
ProductScreen.do.clickDisplayedProduct("Desk Organizer");
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
SelectionPopup.do.clickItem("product_a");
|
||||
PosLoyalty.check.hasRewardLine("Free Product", "-2", "1.00");
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct("Desk Organizer");
|
||||
ProductScreen.do.clickDisplayedProduct("Desk Organizer");
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
SelectionPopup.do.clickItem("product_b");
|
||||
PosLoyalty.check.hasRewardLine("Free Product", "-5", "1.00");
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct("Desk Organizer");
|
||||
ProductScreen.do.clickDisplayedProduct("Desk Organizer");
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
SelectionPopup.do.clickItem("product_b");
|
||||
PosLoyalty.check.hasRewardLine("Free Product", "-10", "2.00");
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
|
||||
Tour.register("PosLoyaltyRewardProductTag", { test: true, url: "/pos/web" }, getSteps());
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
PosLoyalty.do.enterCode('563412');
|
||||
PosLoyalty.check.hasRewardLine('10% on your order', '-1.50');
|
||||
|
||||
Tour.register("test_loyalty_on_order_with_fixed_tax", { test: true, url: "/pos/web" }, getSteps());
|
||||
|
|
@ -1,435 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';
|
||||
import { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';
|
||||
import { getSteps, startSteps } from 'point_of_sale.tour.utils';
|
||||
import Tour from 'web_tour.tour';
|
||||
|
||||
// --- PoS Loyalty Tour Basic Part 1 ---
|
||||
// Generate coupons for PosLoyaltyTour2.
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// basic order
|
||||
// just accept the automatically applied promo program
|
||||
// applied programs:
|
||||
// - on cheapest product
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '5');
|
||||
PosLoyalty.check.hasRewardLine('90% on the cheapest product', '-2.88');
|
||||
PosLoyalty.do.selectRewardLine('on the cheapest product');
|
||||
PosLoyalty.check.orderTotalIs('13.12');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// remove the reward from auto promo program
|
||||
// no applied programs
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '6');
|
||||
PosLoyalty.check.hasRewardLine('on the cheapest product', '-2.88');
|
||||
PosLoyalty.check.orderTotalIs('16.32');
|
||||
PosLoyalty.exec.removeRewardLine('90% on the cheapest product');
|
||||
PosLoyalty.check.orderTotalIs('19.2');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// order with coupon code from coupon program
|
||||
// applied programs:
|
||||
// - coupon program
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '9');
|
||||
PosLoyalty.check.hasRewardLine('on the cheapest product', '-4.59');
|
||||
PosLoyalty.exec.removeRewardLine('90% on the cheapest product');
|
||||
PosLoyalty.check.orderTotalIs('45.90');
|
||||
PosLoyalty.do.enterCode('invalid_code');
|
||||
PosLoyalty.do.enterCode('1234');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-15.30');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Use coupon but eventually remove the reward
|
||||
// applied programs:
|
||||
// - on cheapest product
|
||||
ProductScreen.exec.addOrderline('Letter Tray', '4');
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '9');
|
||||
PosLoyalty.check.hasRewardLine('90% on the cheapest product', '-4.75');
|
||||
PosLoyalty.check.orderTotalIs('62.27');
|
||||
PosLoyalty.do.enterCode('5678');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-15.30');
|
||||
PosLoyalty.check.orderTotalIs('46.97');
|
||||
PosLoyalty.exec.removeRewardLine('Free Product');
|
||||
PosLoyalty.check.orderTotalIs('62.27');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// specific product discount
|
||||
// applied programs:
|
||||
// - on cheapest product
|
||||
// - on specific products
|
||||
ProductScreen.exec.addOrderline('Magnetic Board', '10') // 1.98
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '3') // 5.1
|
||||
ProductScreen.exec.addOrderline('Letter Tray', '4') // 4.8 tax 10%
|
||||
PosLoyalty.check.hasRewardLine('90% on the cheapest product', '-1.78')
|
||||
PosLoyalty.check.orderTotalIs('54.44')
|
||||
PosLoyalty.do.enterCode('promocode')
|
||||
PosLoyalty.check.hasRewardLine('50% on specific products', '-16.66') // 17.55 - 1.78*0.5
|
||||
PosLoyalty.check.orderTotalIs('37.78')
|
||||
PosLoyalty.exec.finalizeOrder('Cash')
|
||||
|
||||
Tour.register('PosLoyaltyTour1', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
// --- PoS Loyalty Tour Basic Part 2 ---
|
||||
// Using the coupons generated from PosLoyaltyTour1.
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Test that global discount and cheapest product discounts can be accumulated.
|
||||
// Applied programs:
|
||||
// - global discount
|
||||
// - on cheapest discount
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '10'); // 5.1
|
||||
PosLoyalty.check.hasRewardLine('on the cheapest product', '-4.59');
|
||||
ProductScreen.exec.addOrderline('Letter Tray', '4'); // 4.8 tax 10%
|
||||
PosLoyalty.check.hasRewardLine('on the cheapest product', '-4.75');
|
||||
PosLoyalty.do.enterCode('123456');
|
||||
PosLoyalty.check.hasRewardLine('10% on your order', '-5.10');
|
||||
PosLoyalty.check.hasRewardLine('10% on your order', '-1.64');
|
||||
PosLoyalty.check.orderTotalIs('60.63'); //SUBTOTAL
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Scanning coupon twice.
|
||||
// Also apply global discount on top of free product to check if the
|
||||
// calculated discount is correct.
|
||||
// Applied programs:
|
||||
// - coupon program (free product)
|
||||
// - global discount
|
||||
// - on cheapest discount
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '11'); // 5.1 per item
|
||||
PosLoyalty.check.hasRewardLine('90% on the cheapest product', '-4.59');
|
||||
PosLoyalty.check.orderTotalIs('51.51');
|
||||
// add global discount and the discount will be replaced
|
||||
PosLoyalty.do.enterCode('345678');
|
||||
PosLoyalty.check.hasRewardLine('10% on your order', '-5.15');
|
||||
// add free product coupon (for qty=11, free=4)
|
||||
// the discount should change after having free products
|
||||
// it should go back to cheapest discount as it is higher
|
||||
PosLoyalty.do.enterCode('5678');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-20.40');
|
||||
PosLoyalty.check.hasRewardLine('90% on the cheapest product', '-4.59');
|
||||
// set quantity to 18
|
||||
// free qty stays the same since the amount of points on the card only allows for 4 free products
|
||||
ProductScreen.do.pressNumpad('Backspace 8')
|
||||
PosLoyalty.check.hasRewardLine('10% on your order', '-6.68');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-20.40');
|
||||
// scan the code again and check notification
|
||||
PosLoyalty.do.enterCode('5678');
|
||||
PosLoyalty.check.orderTotalIs('60.13');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Specific products discount (with promocode) and free product (1357)
|
||||
// Applied programs:
|
||||
// - discount on specific products
|
||||
// - free product
|
||||
ProductScreen.exec.addOrderline('Desk Organizer', '6'); // 5.1 per item
|
||||
PosLoyalty.check.hasRewardLine('on the cheapest product', '-4.59');
|
||||
PosLoyalty.exec.removeRewardLine('90% on the cheapest product');
|
||||
PosLoyalty.do.enterCode('promocode');
|
||||
PosLoyalty.check.hasRewardLine('50% on specific products', '-15.30');
|
||||
PosLoyalty.do.enterCode('1357');
|
||||
PosLoyalty.check.hasRewardLine('Free Product - Desk Organizer', '-10.20');
|
||||
PosLoyalty.check.hasRewardLine('50% on specific products', '-10.20');
|
||||
PosLoyalty.check.orderTotalIs('10.20');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Check reset program
|
||||
// Enter two codes and reset the programs.
|
||||
// The codes should be checked afterwards. They should return to new.
|
||||
// Applied programs:
|
||||
// - cheapest product
|
||||
ProductScreen.exec.addOrderline('Monitor Stand', '6'); // 3.19 per item
|
||||
PosLoyalty.do.enterCode('098765');
|
||||
PosLoyalty.check.hasRewardLine('90% on the cheapest product', '-2.87');
|
||||
PosLoyalty.check.hasRewardLine('10% on your order', '-1.63');
|
||||
PosLoyalty.check.orderTotalIs('14.64');
|
||||
PosLoyalty.exec.removeRewardLine('90% on the cheapest product');
|
||||
PosLoyalty.check.hasRewardLine('10% on your order', '-1.91');
|
||||
PosLoyalty.check.orderTotalIs('17.23');
|
||||
PosLoyalty.do.resetActivePrograms();
|
||||
PosLoyalty.check.hasRewardLine('90% on the cheapest product', '-2.87');
|
||||
PosLoyalty.check.orderTotalIs('16.27');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyTour2', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
// --- PoS Loyalty Tour Basic Part 3 ---
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Promo Product');
|
||||
PosLoyalty.check.orderTotalIs('34.50');
|
||||
ProductScreen.do.clickDisplayedProduct('Product B');
|
||||
PosLoyalty.check.hasRewardLine('100% on specific products', '25.00');
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
PosLoyalty.check.hasRewardLine('100% on specific products', '15.00');
|
||||
PosLoyalty.check.orderTotalIs('34.50');
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
PosLoyalty.check.hasRewardLine('100% on specific products', '21.82');
|
||||
PosLoyalty.check.hasRewardLine('100% on specific products', '18.18');
|
||||
PosLoyalty.check.orderTotalIs('49.50');
|
||||
|
||||
|
||||
Tour.register('PosLoyaltyTour3', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.exec.addOrderline('Test Product 1', '1');
|
||||
ProductScreen.exec.addOrderline('Test Product 2', '1');
|
||||
ProductScreen.do.clickPricelistButton();
|
||||
ProductScreen.do.selectPriceList('Public Pricelist');
|
||||
PosLoyalty.do.enterCode('abcda');
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
ProductScreen.do.clickPricelistButton();
|
||||
ProductScreen.do.selectPriceList('Test multi-currency');
|
||||
PosLoyalty.check.orderTotalIs('0.00');
|
||||
|
||||
Tour.register('PosLoyaltyTour4', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.exec.addOrderline('Test Product 1', '1.00', '100');
|
||||
PosLoyalty.do.clickDiscountButton();
|
||||
PosLoyalty.do.clickConfirmButton();
|
||||
ProductScreen.check.totalAmountIs('92.00');
|
||||
|
||||
Tour.register('PosLoyaltyTour5', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAA Partner');
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product A');
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
ProductScreen.check.totalAmountIs('139');
|
||||
|
||||
Tour.register('PosLoyaltyTour6', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.exec.addOrderline('Test Product', '1');
|
||||
PosLoyalty.check.orderTotalIs('100');
|
||||
PosLoyalty.do.enterCode('abcda');
|
||||
PosLoyalty.check.orderTotalIs('90');
|
||||
|
||||
Tour.register('PosLoyaltyTour7', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Product B');
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
ProductScreen.check.totalAmountIs('50.00');
|
||||
|
||||
Tour.register('PosLoyaltyTour8', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAA Partner');
|
||||
ProductScreen.do.clickDisplayedProduct('Product B');
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
ProductScreen.check.totalAmountIs('210.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
ProductScreen.check.totalAmountIs('205.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
ProductScreen.check.totalAmountIs('200.00');
|
||||
|
||||
Tour.register('PosLoyaltyTour9', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAA Partner');
|
||||
ProductScreen.do.clickDisplayedProduct('Product Test');
|
||||
ProductScreen.check.totalAmountIs('1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.claimReward('Free Product B');
|
||||
PosLoyalty.check.hasRewardLine('Free Product B', '-1.00');
|
||||
ProductScreen.check.totalAmountIs('1.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
|
||||
Tour.register('PosLoyaltyTour10', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAA Partner');
|
||||
PosLoyalty.check.customerIs('AAA Partner');
|
||||
ProductScreen.exec.addOrderline('Product Test', '3');
|
||||
ProductScreen.check.totalAmountIs('150.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyTour11.1', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer('AAA Partner');
|
||||
PosLoyalty.check.customerIs('AAA Partner');
|
||||
ProductScreen.do.clickDisplayedProduct('Product Test');
|
||||
ProductScreen.check.totalAmountIs('50.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
PosLoyalty.do.enterCode('123456');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
PosLoyalty.do.clickRewardButton();
|
||||
PosLoyalty.check.hasRewardLine('Free Product', '-3.00');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(false);
|
||||
ProductScreen.check.totalAmountIs('50.00');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyTour11.2', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
ProductScreen.exec.addOrderline('Free Product A', '2');
|
||||
ProductScreen.do.clickDisplayedProduct('Free Product A');
|
||||
ProductScreen.check.totalAmountIs('2.00');
|
||||
PosLoyalty.check.hasRewardLine('Free Product', '-1.00');
|
||||
|
||||
ProductScreen.exec.addOrderline('Free Product B', '2');
|
||||
ProductScreen.do.clickDisplayedProduct('Free Product B');
|
||||
ProductScreen.check.totalAmountIs('4.00');
|
||||
PosLoyalty.check.hasRewardLine('Free Product', '-2.00');
|
||||
|
||||
ProductScreen.exec.addOrderline('Free Product B', '5');
|
||||
ProductScreen.do.clickDisplayedProduct('Free Product B');
|
||||
ProductScreen.check.totalAmountIs('6.00');
|
||||
PosLoyalty.check.hasRewardLine('Free Product', '-3.00');
|
||||
|
||||
Tour.register('PosLoyaltyTour12', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
ProductScreen.check.selectedOrderlineHas('Product A', '1.00', '20.00');
|
||||
PosLoyalty.check.orderTotalIs('20.00');
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Product B');
|
||||
ProductScreen.check.selectedOrderlineHas('Product B', '1.00', '30.00');
|
||||
PosLoyalty.check.orderTotalIs('50.00');
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Product A');
|
||||
ProductScreen.check.selectedOrderlineHas('Product A', '2.00', '40.00');
|
||||
PosLoyalty.check.orderTotalIs('66.00');
|
||||
|
||||
Tour.register('PosLoyaltyMinAmountAndSpecificProductTour', {test: true, url: '/pos/web'}, getSteps());
|
||||
|
||||
function createOrderCoupon(totalAmount, couponName, couponAmount, loyaltyPoints) {
|
||||
return [
|
||||
ProductScreen.do.confirmOpeningPopup(),
|
||||
ProductScreen.do.clickHomeCategory(),
|
||||
ProductScreen.do.clickPartnerButton(),
|
||||
ProductScreen.do.clickCustomer("partner_a"),
|
||||
ProductScreen.exec.addOrderline("product_a", "1"),
|
||||
ProductScreen.exec.addOrderline("product_b", "1"),
|
||||
PosLoyalty.do.enterCode("promocode"),
|
||||
PosLoyalty.check.hasRewardLine(`${couponName}`, `${couponAmount}`),
|
||||
PosLoyalty.check.orderTotalIs(`${totalAmount}`),
|
||||
PosLoyalty.check.pointsAwardedAre(`${loyaltyPoints}`),
|
||||
PosLoyalty.exec.finalizeOrder("Cash"),
|
||||
];
|
||||
}
|
||||
|
||||
startSteps();
|
||||
createOrderCoupon("135.00", "10% on your order", "-15.00", "135");
|
||||
Tour.register("PosLoyaltyPointsDiscountNoDomainProgramNoDomain", { test: true, url: "/pos/web" }, getSteps());
|
||||
|
||||
startSteps();
|
||||
createOrderCoupon("135.00", "10% on your order", "-15.00", "100");
|
||||
Tour.register("PosLoyaltyPointsDiscountNoDomainProgramDomain", { test: true, url: "/pos/web" }, getSteps());
|
||||
|
||||
startSteps();
|
||||
createOrderCoupon("140.00", "10% on food", "-10.00", "90");
|
||||
Tour.register("PosLoyaltyPointsDiscountWithDomainProgramDomain", { test: true, url: "/pos/web" }, getSteps());
|
||||
|
||||
startSteps();
|
||||
ProductScreen.do.confirmOpeningPopup(),
|
||||
ProductScreen.do.clickHomeCategory(),
|
||||
ProductScreen.do.clickPartnerButton(),
|
||||
ProductScreen.do.clickCustomer("partner_a"),
|
||||
ProductScreen.exec.addOrderline("product_a", "1"),
|
||||
PosLoyalty.check.hasRewardLine('10% on your order', '-10.00');
|
||||
PosLoyalty.check.orderTotalIs('90'),
|
||||
PosLoyalty.check.pointsAwardedAre("90"),
|
||||
PosLoyalty.exec.finalizeOrder("Cash", "90"),
|
||||
Tour.register("PosLoyaltyPointsGlobalDiscountProgramNoDomain", { test: true, url: "/pos/web" }, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer("partner_a");
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product A');
|
||||
PosLoyalty.check.checkNoClaimableRewards();
|
||||
ProductScreen.check.selectedOrderlineHas('Test Product A', '1.00', '100.00');
|
||||
PosLoyalty.exec.finalizeOrder("Cash");
|
||||
|
||||
Tour.register('PosLoyaltyArchivedRewardProductsInactive', {test: true, url: '/pos/web'}, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer("partner_a");
|
||||
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product A');
|
||||
PosLoyalty.check.isRewardButtonHighlighted(true);
|
||||
ProductScreen.check.selectedOrderlineHas('Test Product A', '1.00', '100.00');
|
||||
PosLoyalty.exec.finalizeOrder("Cash");
|
||||
|
||||
Tour.register('PosLoyaltyArchivedRewardProductsActive', {test: true, url: '/pos/web'}, getSteps());
|
||||
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickPartnerButton();
|
||||
ProductScreen.do.clickCustomer("partner_a");
|
||||
|
||||
ProductScreen.exec.addOrderline("Test Product A", "5"),
|
||||
ProductScreen.do.clickDisplayedProduct('Test Product B');
|
||||
PosLoyalty.check.hasRewardLine('10% on your order', '-3.00');
|
||||
PosLoyalty.check.hasRewardLine('10% on Test Product B', '-0.45');
|
||||
PosLoyalty.exec.finalizeOrder("Cash");
|
||||
|
||||
Tour.register('PosLoyalty2DiscountsSpecificGlobal', {test: true, url: '/pos/web'}, getSteps());
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
odoo.define('pos_loyalty.tour.PosCouponTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { createTourMethods } = require('point_of_sale.tour.utils');
|
||||
const { Do: ProductScreenDo } = require('point_of_sale.tour.ProductScreenTourMethods');
|
||||
const { Do: PaymentScreenDo, Check: PaymentScreenCheck } = require('point_of_sale.tour.PaymentScreenTourMethods');
|
||||
const { Do: ReceiptScreenDo } = require('point_of_sale.tour.ReceiptScreenTourMethods');
|
||||
const { Do: ChromeDo } = require('point_of_sale.tour.ChromeTourMethods');
|
||||
|
||||
const ProductScreen = { do: new ProductScreenDo() };
|
||||
const PaymentScreen = { do: new PaymentScreenDo(), check: new PaymentScreenCheck() };
|
||||
const ReceiptScreen = { do: new ReceiptScreenDo() };
|
||||
const Chrome = { do: new ChromeDo() };
|
||||
|
||||
class Do {
|
||||
selectRewardLine(rewardName) {
|
||||
return [
|
||||
{
|
||||
content: 'select reward line',
|
||||
trigger: `.orderline.program-reward .product-name:contains("${rewardName}")`,
|
||||
},
|
||||
{
|
||||
content: 'check reward line if selected',
|
||||
trigger: `.orderline.selected.program-reward .product-name:contains("${rewardName}")`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
];
|
||||
}
|
||||
enterCode(code) {
|
||||
const steps = [
|
||||
{
|
||||
content: 'open code input dialog',
|
||||
trigger: '.control-button:contains("Enter Code")',
|
||||
},
|
||||
{
|
||||
content: `enter code value: ${code}`,
|
||||
trigger: '.popup-textinput input[type="text"]',
|
||||
run: `text ${code}`,
|
||||
},
|
||||
{
|
||||
content: 'confirm inputted code',
|
||||
trigger: '.popup-textinput .button.confirm',
|
||||
},
|
||||
{
|
||||
content: 'verify popup is closed',
|
||||
trigger: 'body:not(:has(.popup-textinput))',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
];
|
||||
return steps;
|
||||
}
|
||||
resetActivePrograms() {
|
||||
return [
|
||||
{
|
||||
content: 'open code input dialog',
|
||||
trigger: '.control-button:contains("Reset Programs")',
|
||||
},
|
||||
];
|
||||
}
|
||||
clickRewardButton() {
|
||||
return [
|
||||
{
|
||||
content: 'open reward dialog',
|
||||
trigger: '.control-button:contains("Reward")',
|
||||
},
|
||||
];
|
||||
}
|
||||
clickEWalletButton(text = 'eWallet') {
|
||||
return [{ trigger: `.control-button:contains("${text}")` }];
|
||||
}
|
||||
claimReward(rewardName) {
|
||||
return [
|
||||
{
|
||||
content: 'open reward dialog',
|
||||
trigger: '.control-button:contains("Reward")',
|
||||
},
|
||||
{
|
||||
content: 'select reward',
|
||||
trigger: `.selection-item:contains("${rewardName}")`,
|
||||
}
|
||||
];
|
||||
}
|
||||
unselectPartner() {
|
||||
return [{ trigger: '.unselect-tag' }];
|
||||
}
|
||||
clickDiscountButton() {
|
||||
return [
|
||||
{
|
||||
content: 'click discount button',
|
||||
trigger: '.js_discount',
|
||||
},
|
||||
];
|
||||
}
|
||||
clickConfirmButton() {
|
||||
return [
|
||||
{
|
||||
content: 'click confirm button',
|
||||
trigger: '.button.confirm',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Check {
|
||||
hasRewardLine(rewardName, amount, qty) {
|
||||
const steps = [
|
||||
{
|
||||
content: 'check if reward line is there',
|
||||
trigger: `.orderline.program-reward span.product-name:contains("${rewardName}")`,
|
||||
run: function () {},
|
||||
},
|
||||
{
|
||||
content: 'check if the reward price is correct',
|
||||
trigger: `.orderline.program-reward span.price:contains("${amount}")`,
|
||||
run: function () {},
|
||||
},
|
||||
];
|
||||
if (qty) {
|
||||
steps.push({
|
||||
content: 'check if the reward qty is correct',
|
||||
trigger: `.order .orderline.program-reward .product-name:contains("${rewardName}") ~ .info-list em:contains("${qty}")`,
|
||||
run: function () {},
|
||||
});
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
orderTotalIs(total_str) {
|
||||
return [
|
||||
{
|
||||
content: 'order total contains ' + total_str,
|
||||
trigger: '.order .total .value:contains("' + total_str + '")',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
];
|
||||
}
|
||||
checkNoClaimableRewards() {
|
||||
return [
|
||||
{
|
||||
content: 'check that no reward can be claimed',
|
||||
trigger: ".control-button:contains('Reward'):not(.highlight)",
|
||||
run: function () {}, // it's a check
|
||||
}
|
||||
]
|
||||
}
|
||||
isRewardButtonHighlighted(isHighlighted) {
|
||||
return [
|
||||
{
|
||||
trigger: isHighlighted
|
||||
? '.control-button.highlight:contains("Reward")'
|
||||
: '.control-button:contains("Reward"):not(:has(.highlight))',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
];
|
||||
}
|
||||
eWalletButtonState({ highlighted, text = 'eWallet' }) {
|
||||
return [
|
||||
{
|
||||
trigger: highlighted
|
||||
? `.control-button.highlight:contains("${text}")`
|
||||
: `.control-button:contains("${text}"):not(:has(.highlight))`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
];
|
||||
}
|
||||
customerIs(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.actionpad button.set-partner:contains("${name}")`,
|
||||
run: function () {},
|
||||
}
|
||||
]
|
||||
}
|
||||
pointsAwardedAre(points_str) {
|
||||
return [
|
||||
{
|
||||
content: 'loyalty points awarded ' + points_str,
|
||||
trigger: '.loyalty-points-won.value:contains("' + points_str + '")',
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class Execute {
|
||||
constructor() {
|
||||
this.do = new Do();
|
||||
this.check = new Check();
|
||||
}
|
||||
finalizeOrder(paymentMethod, amount) {
|
||||
const actions = [
|
||||
...ProductScreen.do.clickPayButton(),
|
||||
...PaymentScreen.do.clickPaymentMethod(paymentMethod),
|
||||
];
|
||||
if (amount) {
|
||||
actions.push(...PaymentScreen.do.pressNumpad([...amount].join(' ')));
|
||||
} else {
|
||||
actions.push(
|
||||
...PaymentScreen.check.remainingIs('0.0'),
|
||||
...PaymentScreen.check.changeIs('0.0'),
|
||||
)
|
||||
}
|
||||
actions.push(
|
||||
...PaymentScreen.do.clickValidate(),
|
||||
...ReceiptScreen.do.clickNextOrder(),
|
||||
);
|
||||
return actions;
|
||||
}
|
||||
removeRewardLine(name) {
|
||||
return [
|
||||
...this.do.selectRewardLine(name),
|
||||
...ProductScreen.do.pressNumpad('Backspace'),
|
||||
...Chrome.do.confirmPopup(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return createTourMethods('PosLoyalty', Do, Check, Execute);
|
||||
});
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { PosLoyalty } from 'pos_loyalty.tour.PosCouponTourMethods';
|
||||
import { ProductScreen } from 'point_of_sale.tour.ProductScreenTourMethods';
|
||||
import { getSteps, startSteps } from 'point_of_sale.tour.utils';
|
||||
import Tour from 'web_tour.tour';
|
||||
|
||||
// First tour should not get any automatic rewards
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.confirmOpeningPopup();
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Not valid -> date
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '5');
|
||||
PosLoyalty.check.checkNoClaimableRewards();
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyValidity1', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
||||
// Second tour
|
||||
startSteps();
|
||||
|
||||
ProductScreen.do.clickHomeCategory();
|
||||
|
||||
// Valid
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '5');
|
||||
PosLoyalty.check.hasRewardLine('90% on the cheapest product', '-2.88');
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
// Not valid -> usage
|
||||
ProductScreen.exec.addOrderline('Whiteboard Pen', '5');
|
||||
PosLoyalty.check.checkNoClaimableRewards();
|
||||
PosLoyalty.exec.finalizeOrder('Cash');
|
||||
|
||||
Tour.register('PosLoyaltyValidity2', { test: true, url: '/pos/web' }, getSteps());
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="PromoCodeButton" owl="1">
|
||||
<span class="control-button">
|
||||
<i class="fa fa-barcode"></i>
|
||||
<span> </span>
|
||||
<span>Enter Code</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="ResetProgramsButton" owl="1">
|
||||
<span class="control-button">
|
||||
<i class="fa fa-star"></i>
|
||||
<span> </span>
|
||||
<span>Reset Programs</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="RewardButton" owl="1">
|
||||
<span class="control-button" t-att-class="hasClaimableRewards() ? 'highlight' : ''">
|
||||
<i class="fa fa-star"></i>
|
||||
<span> </span>
|
||||
<span>Reward</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="point_of_sale.eWalletButton" owl="1">
|
||||
<t t-set="_order" t-value="env.pos.get_order()" />
|
||||
<t t-set="_orderTotal" t-value="_order.get_total_with_tax()" />
|
||||
<t t-set="_eWalletPrograms" t-value="_getEWalletPrograms()" />
|
||||
<t t-set="_eWalletRewards" t-value="_getEWalletRewards(_order)" />
|
||||
<span class="control-button" t-att-class="_shouldBeHighlighted(_orderTotal, _eWalletPrograms, _eWalletRewards) ? 'highlight' : ''" t-on-click="_onClickWalletButton">
|
||||
<i class="fa fa-credit-card"></i>
|
||||
<span> </span>
|
||||
<span><t t-esc="_getText(_orderTotal, _eWalletPrograms, _eWalletRewards)" /></span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="pos_coupon.OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div[hasclass('pos-receipt')]//div[hasclass('before-footer')]" position="inside">
|
||||
<t t-if='receipt.loyaltyStats'>
|
||||
<t t-foreach="receipt.loyaltyStats" t-as="_loyaltyStat" t-key="_loyaltyStat.couponId">
|
||||
<!-- Show only if portal_visible. -->
|
||||
<div t-if="_loyaltyStat.program.portal_visible and (_loyaltyStat.points.won || _loyaltyStat.points.spent)" class='loyalty'>
|
||||
<span class="pos-receipt-center-align">
|
||||
<div>--------------------------------</div>
|
||||
<br/>
|
||||
<div t-esc='_loyaltyStat.program.name' class="pos-receipt-title" />
|
||||
|
||||
<br />
|
||||
</span>
|
||||
<t t-if='_loyaltyStat.points.won'>
|
||||
<div><t t-esc="_loyaltyStat.points.name"/> Won: <span t-esc='_loyaltyStat.points.won' class="pos-receipt-right-align"/></div>
|
||||
</t>
|
||||
<t t-if='_loyaltyStat.points.spent'>
|
||||
<div><t t-esc="_loyaltyStat.points.name"/> Spent: <span t-esc='_loyaltyStat.points.spent' class="pos-receipt-right-align"/></div>
|
||||
</t>
|
||||
<!-- Don't use points.total, it's wrong in this context (after the order synced). -->
|
||||
<!-- Show balance as it's updated during _postPushOrderResolve. -->
|
||||
<t t-if='_loyaltyStat.points.balance'>
|
||||
<div>Balance <t t-esc="_loyaltyStat.points.name"/>: <span t-esc='_loyaltyStat.points.balance' class="pos-receipt-right-align"/></div>
|
||||
</t>
|
||||
<br />
|
||||
</div>
|
||||
</t>
|
||||
<br/>
|
||||
<div>Customer <span t-esc='receipt.partner.name' class='pos-receipt-right-align'/></div>
|
||||
</t>
|
||||
<t t-if="receipt.new_coupon_info and receipt.new_coupon_info.length !== 0">
|
||||
<div class="pos-coupon-rewards">
|
||||
<div>------------------------</div>
|
||||
<br/>
|
||||
<div>
|
||||
Coupon Codes
|
||||
</div>
|
||||
<t t-foreach="receipt.new_coupon_info" t-as="coupon_info" t-key="coupon_info.code">
|
||||
<div class="coupon-container">
|
||||
<div style="font-size: 110%;">
|
||||
<t t-esc="coupon_info['program_name']"/>
|
||||
</div>
|
||||
<div>
|
||||
<span>Valid until: </span>
|
||||
<t t-if="coupon_info['expiration_date']">
|
||||
<t t-esc="coupon_info['expiration_date']"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
no expiration
|
||||
</t>
|
||||
</div>
|
||||
<div>
|
||||
<img t-att-src="'/report/barcode/Code128/'+coupon_info['code']" style="width:200px;height:50px" alt="Barcode"/>
|
||||
</div>
|
||||
<div>
|
||||
<t t-esc="coupon_info['code']"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="OrderSummary" t-inherit="point_of_sale.OrderSummary" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div[hasclass('summary')]" position="after">
|
||||
<div class="summary clearfix">
|
||||
<t t-set="_loyaltyStats" t-value="getLoyaltyPoints()"/>
|
||||
<t t-foreach="_loyaltyStats" t-as="_loyaltyStat" t-key="_loyaltyStat.couponId">
|
||||
<t t-if="_loyaltyStat.points.won || _loyaltyStat.points.spent">
|
||||
<div class='loyalty-points'>
|
||||
<div class='loyalty-points-title'>
|
||||
<t t-esc="_loyaltyStat.points.name"/>
|
||||
</div>
|
||||
<t t-if='_loyaltyStat.points.balance'>
|
||||
<div class="loyalty-points-balance">
|
||||
<span class='value'><t t-esc='_loyaltyStat.points.balance'/></span>
|
||||
</div>
|
||||
</t>
|
||||
<div>
|
||||
<t t-if='_loyaltyStat.points.won'>
|
||||
<span class="value loyalty-points-won">+<t t-esc='_loyaltyStat.points.won'/></span>
|
||||
</t>
|
||||
<t t-if='_loyaltyStat.points.spent'>
|
||||
<span class="value loyalty-points-spent">-<t t-esc='_loyaltyStat.points.spent'/></span>
|
||||
</t>
|
||||
</div>
|
||||
<div class='loyalty-points-total'>
|
||||
<span class='value'><t t-esc='_loyaltyStat.points.total'/></span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div></div>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-name="pos_loyalty.Orderline" t-inherit="point_of_sale.Orderline" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//ul[hasclass('info-list')]" position="attributes">
|
||||
<attribute name="t-if">!_isGiftCardOrEWalletReward()</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//ul[hasclass('info-list')]" position="after">
|
||||
<ul t-else="" class="info-list">
|
||||
Current Balance: <span t-esc="_getGiftCardOrEWalletBalance()"/>
|
||||
</ul>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
import * as PosLoyalty from "@pos_loyalty/../tests/tours/utils/pos_loyalty_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as PartnerList from "@point_of_sale/../tests/pos/tours/utils/partner_list_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import * as Numpad from "@point_of_sale/../tests/generic_helpers/numpad_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import { negateStep } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
import { delay } from "@web/core/utils/concurrency";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("EWalletProgramTour1", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Topup 50$ for partner_aaa
|
||||
ProductScreen.clickDisplayedProduct("Top-up eWallet"),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
ProductScreen.clickPayButton(false),
|
||||
// If there's no partner, we asked to redirect to the partner list screen.
|
||||
Dialog.confirm(),
|
||||
PartnerList.clickPartner("AAAAAAA"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
|
||||
// Topup 10$ for partner_bbb
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("BBBBBBB"),
|
||||
ProductScreen.addOrderline("Top-up eWallet", "1", "10"),
|
||||
PosLoyalty.orderTotalIs("10.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
|
||||
// Check numpad visibility when clicking on eWallet orderline
|
||||
ProductScreen.addOrderline("Whiteboard Pen"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAAA"),
|
||||
PosLoyalty.eWalletButtonState({
|
||||
highlighted: true,
|
||||
text: getEWalletText("Pay"),
|
||||
click: true,
|
||||
}),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
...ProductScreen.clickLine("eWallet"),
|
||||
// Added a small wait because the clickLine function uses a 300ms timeout
|
||||
{
|
||||
content: "Wait 300ms after clicking orderline",
|
||||
trigger: "body",
|
||||
async run() {
|
||||
await delay(300);
|
||||
},
|
||||
},
|
||||
Numpad.isVisible(),
|
||||
...Order.hasLine({
|
||||
withClass: ".selected",
|
||||
run: "click",
|
||||
productName: "eWallet",
|
||||
quantity: "1.0",
|
||||
}),
|
||||
{
|
||||
content: "Wait 300ms after clicking orderline",
|
||||
trigger: "body",
|
||||
async run() {
|
||||
await delay(300);
|
||||
},
|
||||
},
|
||||
negateStep(Numpad.isVisible()),
|
||||
{
|
||||
content: "Click Current Balance line in orderline",
|
||||
trigger: ".orderline li:contains(Current Balance:)",
|
||||
run: "click",
|
||||
},
|
||||
Numpad.isVisible(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
const getEWalletText = (suffix) => "eWallet" + (suffix !== "" ? ` ${suffix}` : "");
|
||||
registry.category("web_tour.tours").add("EWalletProgramTour2", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "2", "6", "12.00"),
|
||||
PosLoyalty.eWalletButtonState({ highlighted: false }),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAAA"),
|
||||
PosLoyalty.eWalletButtonState({
|
||||
highlighted: true,
|
||||
text: getEWalletText("Pay"),
|
||||
click: true,
|
||||
}),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
|
||||
// Consume partner_bbb's full eWallet.
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("BBBBBBB"),
|
||||
PosLoyalty.eWalletButtonState({ highlighted: false }),
|
||||
ProductScreen.addOrderline("Desk Pad", "6", "6", "36.00"),
|
||||
PosLoyalty.eWalletButtonState({
|
||||
highlighted: true,
|
||||
text: getEWalletText("Pay"),
|
||||
click: true,
|
||||
}),
|
||||
PosLoyalty.eWalletButtonState({ highlighted: false }),
|
||||
PosLoyalty.orderTotalIs("26.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "26"),
|
||||
|
||||
// Switching partners should work.
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("BBBBBBB"),
|
||||
ProductScreen.addOrderline("Desk Pad", "2", "19", "38.00"),
|
||||
PosLoyalty.eWalletButtonState({ highlighted: false }),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAAA"),
|
||||
PosLoyalty.eWalletButtonState({
|
||||
highlighted: true,
|
||||
text: getEWalletText("Pay"),
|
||||
click: true,
|
||||
}),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("BBBBBBB"),
|
||||
PosLoyalty.eWalletButtonState({ highlighted: false }),
|
||||
PosLoyalty.orderTotalIs("38.00"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAAA"),
|
||||
PosLoyalty.eWalletButtonState({
|
||||
highlighted: true,
|
||||
text: getEWalletText("Pay"),
|
||||
click: true,
|
||||
}),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
|
||||
// Refund with eWallet.
|
||||
// - Make an order to refund.
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("BBBBBBB"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1", "20", "20.00"),
|
||||
PosLoyalty.orderTotalIs("20.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
// - Refund order.
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.filterIs("Paid"),
|
||||
TicketScreen.selectOrder("2004"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
ProductScreen.isShown(),
|
||||
PosLoyalty.eWalletButtonState({
|
||||
highlighted: true,
|
||||
text: getEWalletText("Refund"),
|
||||
click: true,
|
||||
}),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("ExpiredEWalletProgramTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "2", "6", "12.00"),
|
||||
PosLoyalty.eWalletButtonState({ highlighted: false, click: true }),
|
||||
Dialog.is({ title: "No valid eWallet found" }),
|
||||
Dialog.confirm(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyPointsEwallet", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
PosLoyalty.eWalletButtonState({ highlighted: false }),
|
||||
ProductScreen.addOrderline("product_a", "1"),
|
||||
PosLoyalty.eWalletButtonState({
|
||||
highlighted: true,
|
||||
text: getEWalletText("Pay"),
|
||||
click: true,
|
||||
}),
|
||||
PosLoyalty.pointsAwardedAre("100"),
|
||||
PosLoyalty.finalizeOrder("Cash", "90.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("EWalletLoyaltyHistory", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Top-up eWallet"),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
PartnerList.clickPartner("AAAAAAA"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "2", "6", "12.00"),
|
||||
PosLoyalty.eWalletButtonState({ highlighted: false }),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAAA"),
|
||||
PosLoyalty.eWalletButtonState({
|
||||
highlighted: true,
|
||||
text: getEWalletText("Pay"),
|
||||
click: true,
|
||||
}),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
import * as PosLoyalty from "@pos_loyalty/../tests/tours/utils/pos_loyalty_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
|
||||
registry.category("web_tour.tours").add("GiftCardProgramTour1", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("GiftCardProgramTour2", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.enterCode("044123456"),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("GiftCardWithRefundtTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Magnetic Board", "1"), // 1.98
|
||||
PosLoyalty.orderTotalIs("1.98"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
Order.hasLine({
|
||||
withClass: ".selected",
|
||||
productName: "Magnetic Board",
|
||||
}),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
PaymentScreen.clickBack(),
|
||||
ProductScreen.isShown(),
|
||||
ProductScreen.clickLine("Magnetic Board", "-1"),
|
||||
ProductScreen.selectedOrderlineHas("Magnetic Board", "-1"),
|
||||
ProductScreen.addOrderline("Gift Card", "1"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1"),
|
||||
PosLoyalty.orderTotalIs("0.0"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("GiftCardProgramPriceNoTaxTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
// Use gift card
|
||||
ProductScreen.addOrderline("Magnetic Board", "1", "1.98", "1.98"),
|
||||
PosLoyalty.enterCode("043123456"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.clickOrderline("Gift Card"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1", "-1.00"),
|
||||
PosLoyalty.orderTotalIs("0.98"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyPointsGiftcard", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Gift Card", "1", "50", "50"),
|
||||
PosLoyalty.createManualGiftCard("044123456", 50),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
ProductScreen.addOrderline("product_a", "1"),
|
||||
PosLoyalty.enterCode("044123456"),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
PosLoyalty.pointsAwardedAre("100"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyGiftCardTaxes", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Gift Card", "1", "50", "50"),
|
||||
PosLoyalty.createManualGiftCard("044123456", 50),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product A"),
|
||||
PosLoyalty.enterCode("044123456"),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
ProductScreen.checkTaxAmount("-6.52"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PhysicalGiftCardProgramSaleTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Gift Card", "1", "50", "50"),
|
||||
PosLoyalty.createManualGiftCard("test-card-0000", 125),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1", "125"),
|
||||
PosLoyalty.orderTotalIs("125"),
|
||||
PosLoyalty.finalizeOrder("Cash", "125"),
|
||||
ProductScreen.addOrderline("Gift Card", "1", "50", "50"),
|
||||
PosLoyalty.createManualGiftCard("test-card-0001", 100),
|
||||
PosLoyalty.clickPhysicalGiftCard("test-card-0001"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1", "100"),
|
||||
ProductScreen.addOrderline("Gift Card", "1", "50", "50"),
|
||||
PosLoyalty.createManualGiftCard("new-card-0001", 250),
|
||||
PosLoyalty.clickPhysicalGiftCard("new-card-0001"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1", "250"),
|
||||
PosLoyalty.orderTotalIs("350"),
|
||||
PosLoyalty.finalizeOrder("Cash", "350"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("MultiplePhysicalGiftCardProgramSaleTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
PosLoyalty.clickGiftCardProgram("Gift Cards1"),
|
||||
PosLoyalty.createManualGiftCard("test-card-0000", 125),
|
||||
PosLoyalty.clickGiftCardProgram("Gift Cards"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1.00", "125"),
|
||||
PosLoyalty.orderTotalIs("125"),
|
||||
PosLoyalty.finalizeOrder("Cash", "125"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
PosLoyalty.clickGiftCardProgram("Gift Cards2"),
|
||||
PosLoyalty.createManualGiftCard("test-card-0001", 125),
|
||||
PosLoyalty.clickGiftCardProgram("Gift Cards2"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1.00", "125"),
|
||||
PosLoyalty.orderTotalIs("125"),
|
||||
PosLoyalty.finalizeOrder("Cash", "125"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
PosLoyalty.clickGiftCardProgram("Gift Cards3"),
|
||||
PosLoyalty.createManualGiftCard("test-card-0002", 125),
|
||||
PosLoyalty.clickGiftCardProgram("Gift Cards3"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1.00", "125"),
|
||||
PosLoyalty.orderTotalIs("125"),
|
||||
PosLoyalty.finalizeOrder("Cash", "125"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("GiftCardProgramInvoice", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("A Test Partner"),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickInvoiceButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_gift_card_no_date", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
PosLoyalty.createManualGiftCard("test", "42", ""),
|
||||
PosLoyalty.finalizeOrder("Cash", "42"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_physical_gift_card_invoiced", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AABBCC Test Partner"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
PosLoyalty.createManualGiftCard("test-card-1234", 125),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1.00", "125"),
|
||||
PosLoyalty.orderTotalIs("125"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickInvoiceButton(),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("EmptyProductScreenTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.isEmpty(),
|
||||
ProductScreen.loadSampleButtonIsThere(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_physical_gift_card", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
|
||||
// Gift card cannot be used as it's already linked to a partner
|
||||
PosLoyalty.useExistingLoyaltyCard("gift_card_partner", false),
|
||||
// Gift card cannot be used as it's expired
|
||||
PosLoyalty.useExistingLoyaltyCard("gift_card_expired", false),
|
||||
// Gift card is already sold
|
||||
PosLoyalty.useExistingLoyaltyCard("gift_card_sold", false),
|
||||
|
||||
// Use gift_card_generated_but_not_sold - Warning is triggered
|
||||
PosLoyalty.enterCode("gift_card_generated_but_not_sold"),
|
||||
Dialog.cancel(),
|
||||
|
||||
// Sell the unsold gift card
|
||||
PosLoyalty.useExistingLoyaltyCard("gift_card_generated_but_not_sold", true),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1", "60.00"),
|
||||
ProductScreen.clickNumpad("2"), // Cannot edit a physical gift card
|
||||
Dialog.confirm(), // Warning is triggered
|
||||
PosLoyalty.orderTotalIs("60.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "60"),
|
||||
|
||||
// Use gift_card_valid - No warning should be triggered
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.enterCode("gift_card_valid"),
|
||||
ProductScreen.clickLine("Gift Card"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1", "-3.20"),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
|
||||
// Use gift_card_partner - No warning should be triggered
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.enterCode("gift_card_partner"),
|
||||
ProductScreen.clickLine("Gift Card"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1", "-3.20"),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
|
||||
// Use gift_card_sold - Warning should be triggered
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.enterCode("gift_card_sold"),
|
||||
ProductScreen.clickLine("Gift Card"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1", "-6.40"),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
|
||||
// Use gift_card_generated_but_not_sold - No warning should be triggered
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.enterCode("gift_card_generated_but_not_sold"),
|
||||
ProductScreen.clickLine("Gift Card"),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1", "-12.80"),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
|
||||
// Try to use expired gift card
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.enterCode("gift_card_expired"),
|
||||
PosLoyalty.orderTotalIs("9.60"),
|
||||
PosLoyalty.finalizeOrder("Cash", "9.60"),
|
||||
|
||||
// Sell a new gift card with a partner
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("A powerful PoS man!"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
ProductScreen.clickNumpad("Price", "9", "9", "9"),
|
||||
PosLoyalty.orderTotalIs("999.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "999"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_multiple_physical_gift_card_sale", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
PosLoyalty.createManualGiftCard("test-card-0000", 125),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card", "1.00", "125"),
|
||||
ProductScreen.addOrderline("Gift Card", "1", "0", "0"),
|
||||
PosLoyalty.createManualGiftCard("test-card-0001", 100),
|
||||
PosLoyalty.finalizeOrder("Cash", "225"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import * as PosLoyalty from "@pos_loyalty/../tests/tours/utils/pos_loyalty_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("LoyaltyHistoryTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Test Partner"),
|
||||
PosLoyalty.orderTotalIs("10"),
|
||||
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import * as PosLoyalty from "@pos_loyalty/../tests/tours/utils/pos_loyalty_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as SelectionPopup from "@point_of_sale/../tests/generic_helpers/selection_popup_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const getEWalletText = (suffix) => "eWallet" + (suffix !== "" ? ` ${suffix}` : "");
|
||||
registry.category("web_tour.tours").add("MultipleGiftWalletProgramsTour", {
|
||||
steps: () =>
|
||||
[
|
||||
// One card for gift_card_1.
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
SelectionPopup.has("gift_card_1"),
|
||||
SelectionPopup.has("gift_card_2"),
|
||||
SelectionPopup.has("gift_card_1", { run: "click" }),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card"),
|
||||
ProductScreen.clickNumpad("Price"),
|
||||
ProductScreen.modeIsActive("Price"),
|
||||
ProductScreen.clickNumpad("1", "0"),
|
||||
PosLoyalty.orderTotalIs("10.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
// One card for gift_card_1.
|
||||
ProductScreen.clickDisplayedProduct("Gift Card"),
|
||||
SelectionPopup.has("gift_card_2", { run: "click" }),
|
||||
ProductScreen.selectedOrderlineHas("Gift Card"),
|
||||
ProductScreen.clickNumpad("Price"),
|
||||
ProductScreen.modeIsActive("Price"),
|
||||
ProductScreen.clickNumpad("2", "0"),
|
||||
PosLoyalty.orderTotalIs("20.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
// Top up ewallet_1 for AAAAAAA.
|
||||
ProductScreen.clickDisplayedProduct("Top-up eWallet"),
|
||||
SelectionPopup.has("ewallet_1"),
|
||||
SelectionPopup.has("ewallet_2"),
|
||||
SelectionPopup.has("ewallet_1", { run: "click" }),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAAA"),
|
||||
ProductScreen.clickNumpad("Price"),
|
||||
ProductScreen.modeIsActive("Price"),
|
||||
ProductScreen.clickNumpad("3", "0"),
|
||||
PosLoyalty.orderTotalIs("30.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "30"),
|
||||
// Top up ewallet_2 for AAAAAAA.
|
||||
ProductScreen.clickDisplayedProduct("Top-up eWallet"),
|
||||
SelectionPopup.has("ewallet_2", { run: "click" }),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAAA"),
|
||||
ProductScreen.clickNumpad("Price"),
|
||||
ProductScreen.modeIsActive("Price"),
|
||||
ProductScreen.clickNumpad("4", "0"),
|
||||
PosLoyalty.orderTotalIs("40.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "40"),
|
||||
// Top up ewallet_1 for BBBBBBB.
|
||||
ProductScreen.clickDisplayedProduct("Top-up eWallet"),
|
||||
SelectionPopup.has("ewallet_1", { run: "click" }),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("BBBBBBB"),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
// Consume 12$ from ewallet_1 of AAAAAAA.
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "2", "6", "12.00"),
|
||||
PosLoyalty.eWalletButtonState({ highlighted: false }),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAAAAA"),
|
||||
PosLoyalty.eWalletButtonState({
|
||||
highlighted: true,
|
||||
text: getEWalletText("Pay"),
|
||||
click: true,
|
||||
}),
|
||||
SelectionPopup.has("ewallet_1"),
|
||||
SelectionPopup.has("ewallet_2"),
|
||||
SelectionPopup.has("ewallet_1", { run: "click" }),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
import * as PosLoyalty from "@pos_loyalty/../tests/tours/utils/pos_loyalty_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as combo from "@point_of_sale/../tests/pos/tours/utils/combo_popup_util";
|
||||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import { inLeftSide } from "@point_of_sale/../tests/pos/tours/utils/common";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { negate } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyLoyaltyProgram1", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Order1: Generates 2 points.
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "2"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Test Partner"),
|
||||
PosLoyalty.orderTotalIs("6.40"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
|
||||
// Order2: Consumes points to get free product.
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Test Partner"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "1"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "2"),
|
||||
// At this point, AAA Test Partner has 4 points.
|
||||
PosLoyalty.isMoreControlButtonActive(true),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "3"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1"),
|
||||
PosLoyalty.isMoreControlButtonActive(false),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
PosLoyalty.orderTotalIs("6.40"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
|
||||
// Order3: Generate 4 points.
|
||||
// - Initially gets free product, but was removed automatically by changing the
|
||||
// number of items to zero.
|
||||
// - It's impossible to checked here if the free product reward is really removed
|
||||
// so we check in the backend the number of orders that consumed the reward.
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "4"),
|
||||
PosLoyalty.orderTotalIs("12.80"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Test Partner"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.clickNumpad("⌫"),
|
||||
// At this point, the reward line should have been automatically removed
|
||||
// because there is not enough points to purchase it. Unfortunately, we
|
||||
// can't check that here.
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "1.00"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "2.00"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "3.00"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
ProductScreen.selectedOrderlineHas("Whiteboard Pen", "4.00"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
|
||||
PosLoyalty.orderTotalIs("12.80"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyLoyaltyProgram2", {
|
||||
steps: () =>
|
||||
[
|
||||
// Order1: Immediately set the customer to Test Partner AAA which has 4 points.
|
||||
// - He has enough points to purchase a free product but since there is still
|
||||
// no product in the order, reward button should not yet be highlighted.
|
||||
// - Furthermore, clicking the reward product should not add it as reward product.
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Test Partner"),
|
||||
// No item in the order, so reward button is off.
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
PosLoyalty.orderTotalIs("3.20"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
|
||||
// Order2: Generate 4 points for CCC Test Partner.
|
||||
// - Reference: Order2_CCC
|
||||
// - But set BBB Test Partner first as the customer.
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("BBB Test Partner"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "1"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "2"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "3"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "4"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("CCC Test Partner"),
|
||||
PosLoyalty.customerIs("CCC Test Partner"),
|
||||
PosLoyalty.orderTotalIs("12.80"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
|
||||
// Order3: Generate 3 points for BBB Test Partner.
|
||||
// - Reference: Order3_BBB
|
||||
// - But set CCC Test Partner first as the customer.
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("CCC Test Partner"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "3"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("BBB Test Partner"),
|
||||
PosLoyalty.customerIs("BBB Test Partner"),
|
||||
PosLoyalty.orderTotalIs("9.60"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
|
||||
// Order4: Should not have reward because the customer will be removed.
|
||||
// - Reference: Order4_no_reward
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen", true, "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("CCC Test Partner"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
PosLoyalty.claimReward("Free Product - Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
// This deselects the customer.
|
||||
PosLoyalty.unselectPartner(),
|
||||
PosLoyalty.customerIs("Customer"),
|
||||
PosLoyalty.orderTotalIs("6.40"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyChangeRewardQty", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("DDD Test Partner"),
|
||||
ProductScreen.addOrderline("Desk Organizer", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
PosLoyalty.claimReward("Free Product - Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1"),
|
||||
ProductScreen.clickNumpad("Qty"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyLoyaltyProgram3", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Generates 10.2 points and use points to get the reward product with zero sale price
|
||||
ProductScreen.addOrderline("Desk Organizer", "2"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Test Partner"),
|
||||
|
||||
// At this point, the free_product program is triggered.
|
||||
// The reward button should be highlighted.
|
||||
PosLoyalty.isRewardButtonHighlighted(true, true),
|
||||
PosLoyalty.claimReward("Free Product - Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-1.00", "1"),
|
||||
|
||||
PosLoyalty.orderTotalIs("10.2"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10.2"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyPromotion", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Partner"),
|
||||
ProductScreen.addOrderline("Test Product 1", "1", "100"),
|
||||
ProductScreen.totalAmountIs("90.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyDontGrantPointsForRewardOrderLines", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("A Test Partner"),
|
||||
|
||||
ProductScreen.addOrderline("Desk Organizer", "1"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true, true),
|
||||
PosLoyalty.claimReward("100% on the cheapest product"),
|
||||
|
||||
PosLoyalty.orderTotalIs("5.10"),
|
||||
PosLoyalty.finalizeOrder("Cash", "5.10"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyNextOrderCouponExpirationDate", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.addOrderline("Desk Organizer", "3"),
|
||||
|
||||
PosLoyalty.finalizeOrder("Cash", "15.3"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosComboCheapestRewardProgram", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Expensive product"),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 1"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
inLeftSide(Order.hasLine({ productName: "10% on the cheapest product" })),
|
||||
PosLoyalty.orderTotalIs("1,204.25"),
|
||||
PosLoyalty.finalizeOrder("Cash", "1204.25"),
|
||||
ProductScreen.clickDisplayedProduct("Cheap product"),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 1"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
Order.hasLine({ productName: "10% on the cheapest product" }),
|
||||
PosLoyalty.orderTotalIs("61.03"),
|
||||
PosLoyalty.finalizeOrder("Cash", "61.03"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosComboSpecificProductProgram", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 1"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
Order.hasLine({ productName: "10% on Office Combo" }),
|
||||
PosLoyalty.orderTotalIs("216.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "216.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosCheapestProductTaxInclude", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Product"),
|
||||
ProductScreen.addOrderline("Desk Organizer", "1"),
|
||||
Order.hasLine({ productName: "10% on the cheapest product" }),
|
||||
PosLoyalty.orderTotalIs("6.00"), // taxe of 9 cents (≈ 10% HT)
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_not_create_loyalty_card_expired_program", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Test Partner", true),
|
||||
ProductScreen.addOrderline("Desk Organizer", "3"),
|
||||
PosLoyalty.finalizeOrder("Cash", "15.3"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosOrderClaimReward", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Test Partner"),
|
||||
ProductScreen.addOrderline("Desk Organizer", "3"),
|
||||
PosLoyalty.isPointsDisplayed(true),
|
||||
PosLoyalty.claimReward("Free Product - Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "15.3"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosOrderNoPoints", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Test Partner 2"),
|
||||
ProductScreen.addOrderline("Desk Organizer", "3"),
|
||||
PosLoyalty.isPointsDisplayed(false),
|
||||
PosLoyalty.finalizeOrder("Cash", "15.3"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyMultipleOrders", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Order1: Add a product and leave the order in draft.
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "2"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 1"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
|
||||
// Order2: Finalize a different order.
|
||||
Chrome.createFloatingOrder(),
|
||||
ProductScreen.addOrderline("Desk Organizer", "1"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_combo_product_dont_grant_point", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 1"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.clickDisplayedProduct("Office Combo"),
|
||||
combo.select("Combo Product 1"),
|
||||
combo.select("Combo Product 4"),
|
||||
combo.select("Combo Product 6"),
|
||||
Dialog.confirm(),
|
||||
Order.hasLine({ productName: "100% on the cheapest product" }),
|
||||
ProductScreen.totalAmountIs("50.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_buy_x_get_y_reward_qty", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "10"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1.00"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-6.40", "2.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "32"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "10"),
|
||||
PosLoyalty.claimReward("Free Product - Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-9.60", "3.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "32"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_multiple_loyalty_products", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
{
|
||||
content: "Check that selection popup is not opened",
|
||||
trigger: negate(`.selection-item`),
|
||||
},
|
||||
Order.hasLine({ productName: "Whiteboard Pen", quantity: "1" }),
|
||||
Order.hasLine({ productName: "10% on your order", quantity: "1" }),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_max_usage_partner_with_point", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Desk Organizer", "3"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Partner 2"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
PosLoyalty.claimReward("100% on your order"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Partner"),
|
||||
ProductScreen.addOrderline("Desk Organizer", "3"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as PosLoyalty from "@pos_loyalty/../tests/tours/utils/pos_loyalty_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as SelectionPopup from "@point_of_sale/../tests/generic_helpers/selection_popup_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
import * as ProductConfiguratorPopup from "@point_of_sale/../tests/pos/tours/utils/product_configurator_util";
|
||||
import { negateStep } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
import * as PartnerList from "@point_of_sale/../tests/pos/tours/utils/partner_list_util";
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyFreeProductTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.addOrderline("Desk Organizer", "2"),
|
||||
|
||||
// At this point, the free_product program is triggered.
|
||||
// The reward button should be highlighted.
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
// Since the reward button is highlighted, clicking the reward product should be added as reward.
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", "3"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-5.10", "1"),
|
||||
// In the succeeding 2 clicks on the product, it is considered as a regular product.
|
||||
// In the third click, the product will be added as reward.
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", true, "6"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-10.20", "2"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
PosLoyalty.orderTotalIs("25.50"),
|
||||
// Finalize order that consumed a reward.
|
||||
PosLoyalty.finalizeOrder("Cash", "30"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", true, "1"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", true, "2"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-5.10", "1"),
|
||||
ProductScreen.clickNumpad("9"),
|
||||
ProductScreen.selectedOrderlineHas("Desk Organizer", "9"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-15.30", "3"),
|
||||
ProductScreen.clickNumpad("⌫"),
|
||||
ProductScreen.selectedOrderlineHas("Desk Organizer", "0"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", true, "1"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer", true, "2"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
// Finalize order but without the reward.
|
||||
// This step is important. When syncing the order, no reward should be synced.
|
||||
PosLoyalty.orderTotalIs("10.20"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
|
||||
ProductScreen.addOrderline("Magnetic Board", "2"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.clickDisplayedProduct("Magnetic Board"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1"),
|
||||
ProductScreen.clickOrderline("Magnetic Board", "3"),
|
||||
ProductScreen.selectedOrderlineHas("Magnetic Board", "3"),
|
||||
ProductScreen.clickNumpad("6"),
|
||||
ProductScreen.selectedOrderlineHas("Magnetic Board", "6"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
PosLoyalty.claimReward("Free Product - Whiteboard Pen"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-6.40", "2"),
|
||||
// Finalize order that consumed a reward.
|
||||
PosLoyalty.orderTotalIs("11.88"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
|
||||
ProductScreen.addOrderline("Magnetic Board", "6"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
|
||||
ProductScreen.clickOrderline("Magnetic Board", "6"),
|
||||
ProductScreen.clickNumpad("⌫"),
|
||||
// At this point, the reward should have been removed.
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.selectedOrderlineHas("Magnetic Board", "0"),
|
||||
ProductScreen.clickDisplayedProduct("Magnetic Board"),
|
||||
ProductScreen.selectedOrderlineHas("Magnetic Board", "1"),
|
||||
ProductScreen.clickDisplayedProduct("Magnetic Board"),
|
||||
ProductScreen.selectedOrderlineHas("Magnetic Board", "2"),
|
||||
ProductScreen.clickDisplayedProduct("Magnetic Board"),
|
||||
ProductScreen.selectedOrderlineHas("Magnetic Board", "3"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Whiteboard Pen", "-3.20", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
|
||||
PosLoyalty.orderTotalIs("5.94"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
|
||||
// Promotion: 2 items of shelves, get desk_pad/monitor_stand free
|
||||
// This is the 5th order.
|
||||
ProductScreen.clickDisplayedProduct("Wall Shelf Unit"),
|
||||
ProductScreen.selectedOrderlineHas("Wall Shelf Unit", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.clickDisplayedProduct("Small Shelf"),
|
||||
ProductScreen.selectedOrderlineHas("Small Shelf", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
// Click reward product. Should be automatically added as reward.
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-1.98", "1"),
|
||||
// Remove the reward line. The next steps will check if cashier
|
||||
// can select from the different reward products.
|
||||
ProductScreen.clickNumpad("⌫"),
|
||||
ProductScreen.clickNumpad("⌫"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
PosLoyalty.claimReward("Free Product - [Desk Pad, Monitor Stand]"),
|
||||
SelectionPopup.has("Monitor Stand"),
|
||||
SelectionPopup.has("Desk Pad"),
|
||||
SelectionPopup.has("Desk Pad", { run: "click" }),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-1.98", "1"),
|
||||
ProductScreen.clickNumpad("⌫"),
|
||||
ProductScreen.clickNumpad("⌫"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
PosLoyalty.claimReward("Free Product - [Desk Pad, Monitor Stand]"),
|
||||
SelectionPopup.has("Monitor Stand"),
|
||||
SelectionPopup.has("Desk Pad"),
|
||||
SelectionPopup.has("Monitor Stand", { run: "click" }),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.selectedOrderlineHas("Monitor Stand", "1", "3.19"),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-3.19", "1"),
|
||||
PosLoyalty.orderTotalIs("4.81"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyFreeProductTour2", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Partner"),
|
||||
ProductScreen.addOrderline("Test Product A", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true, true),
|
||||
ProductScreen.clickControlButton("Reward"),
|
||||
SelectionPopup.has("Free Product - Test Product A", { run: "click" }),
|
||||
PosLoyalty.hasRewardLine("Free Product - Test Product A", "-11.50", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_loyalty_free_product_rewards_2", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-5.10", "1.00"),
|
||||
PosLoyalty.orderTotalIs("10.20"),
|
||||
PosLoyalty.finalizeOrder("Cash", "10.20"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltySpecificDiscountTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Test Product A"),
|
||||
ProductScreen.selectedOrderlineHas("Test Product A", "1", "40.00"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product B"),
|
||||
ProductScreen.selectedOrderlineHas("Test Product B", "1", "40.00"),
|
||||
ProductScreen.clickControlButton("Reward"),
|
||||
SelectionPopup.has("$ 10 on specific products", { run: "click" }),
|
||||
PosLoyalty.hasRewardLine("$ 10 on specific products", "-10.00", "1"),
|
||||
PosLoyalty.orderTotalIs("70.00"),
|
||||
ProductScreen.clickControlButton("Reward"),
|
||||
SelectionPopup.has("$ 10 on specific products", { run: "click" }),
|
||||
PosLoyalty.orderTotalIs("60.00"),
|
||||
ProductScreen.clickControlButton("Reward"),
|
||||
SelectionPopup.has("$ 30 on specific products", { run: "click" }),
|
||||
PosLoyalty.hasRewardLine("$ 30 on specific products", "-30.00", "1"),
|
||||
PosLoyalty.orderTotalIs("30.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltySpecificDiscountWithFreeProductTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product A"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product C"),
|
||||
PosLoyalty.orderTotalIs("130.00"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true, false),
|
||||
{
|
||||
content: `click Reward button`,
|
||||
trigger: ProductScreen.controlButtonTrigger("Reward"),
|
||||
run: "click",
|
||||
},
|
||||
Dialog.cancel(),
|
||||
PosLoyalty.orderTotalIs("130.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltySpecificDiscountWithRewardProductDomainTour", {
|
||||
steps: () =>
|
||||
[
|
||||
// Steps to check if the alert dialog for invalid domain loyalty program is present, only then will the pos screen load correctly
|
||||
Dialog.is("A reward could not be loaded"),
|
||||
Dialog.confirm("Ok"),
|
||||
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.selectedOrderlineHas("Product A", "1", "15.00"),
|
||||
PosLoyalty.orderTotalIs("15.00"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
ProductScreen.selectedOrderlineHas("Product B", "1", "50.00"),
|
||||
PosLoyalty.orderTotalIs("40.00"),
|
||||
|
||||
ProductScreen.clickControlButton("Reward"),
|
||||
SelectionPopup.has("10$ on your order - Product B - Saleable", { run: "click" }),
|
||||
ProductScreen.clickControlButton("Reward"),
|
||||
SelectionPopup.has("10$ on your order - Product B - Not Saleable", { run: "click" }),
|
||||
PosLoyalty.orderTotalIs("30.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyRewardProductTag", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true, true),
|
||||
PosLoyalty.claimReward("Free Product - [Product A, Product B]"),
|
||||
SelectionPopup.has("Product A", { run: "click" }),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-2", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false, true),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true, true),
|
||||
PosLoyalty.claimReward("Free Product - [Product A, Product B]"),
|
||||
SelectionPopup.has("Product B", { run: "click" }),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-5", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false, true),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true, true),
|
||||
PosLoyalty.claimReward("Free Product - [Product A, Product B]"),
|
||||
SelectionPopup.has("Product B", { run: "click" }),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-10", "2"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false, true),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_loyalty_on_order_with_fixed_tax", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
PosLoyalty.enterCode("563412"),
|
||||
PosLoyalty.hasRewardLine("10% on your order", "-1.50"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_loyalty_reward_with_variant", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
PartnerList.searchCustomerValue("Test Partner", true),
|
||||
ProductScreen.clickCustomer("Test Partner"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product"),
|
||||
Dialog.discard(),
|
||||
ProductScreen.clickDisplayedProduct("Test Product"),
|
||||
ProductConfiguratorPopup.pickRadio("Value 1"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.clickDisplayedProduct("Test Product"),
|
||||
ProductConfiguratorPopup.pickRadio("Value 1"),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.clickDisplayedProduct("Test Product"),
|
||||
ProductConfiguratorPopup.pickRadio("Value 1"),
|
||||
Dialog.confirm(),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-10", "1.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_multiple_reward_line_free_product", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Product A", "-10", "1.00"),
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Product B").map(negateStep),
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Product B", "-5", "1.00"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Product A", "-10", "1.00"),
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Product B", "-5", "1.00"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Product A", "-10", "1.00"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Product B", "-5", "1.00"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Product A", "-20", "2.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,806 @@
|
|||
/* global posmodel */
|
||||
import * as PosLoyalty from "@pos_loyalty/../tests/tours/utils/pos_loyalty_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as TicketScreen from "@point_of_sale/../tests/pos/tours/utils/ticket_screen_util";
|
||||
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
|
||||
import * as SelectionPopup from "@point_of_sale/../tests/generic_helpers/selection_popup_util";
|
||||
import * as PartnerList from "@point_of_sale/../tests/pos/tours/utils/partner_list_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import * as Notification from "@point_of_sale/../tests/generic_helpers/notification_util";
|
||||
import * as Utils from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { scan_barcode } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour1", {
|
||||
steps: () =>
|
||||
[
|
||||
// --- PoS Loyalty Tour Basic Part 1 ---
|
||||
// Generate coupons for PosLoyaltyTour2.
|
||||
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// basic order
|
||||
// just accept the automatically applied promo program
|
||||
// applied programs:
|
||||
// - on cheapest product
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "5"),
|
||||
PosLoyalty.hasRewardLine("90% on the cheapest product", "-2.88"),
|
||||
PosLoyalty.selectRewardLine("on the cheapest product"),
|
||||
PosLoyalty.orderTotalIs("13.12"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
|
||||
// remove the reward from auto promo program
|
||||
// no applied programs
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "6"),
|
||||
PosLoyalty.hasRewardLine("on the cheapest product", "-2.88"),
|
||||
PosLoyalty.orderTotalIs("16.32"),
|
||||
PosLoyalty.removeRewardLine("90% on the cheapest product"),
|
||||
PosLoyalty.orderTotalIs("19.2"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
|
||||
// order with coupon code from coupon program
|
||||
// applied programs:
|
||||
// - coupon program
|
||||
ProductScreen.addOrderline("Desk Organizer", "9"),
|
||||
PosLoyalty.hasRewardLine("on the cheapest product", "-4.59"),
|
||||
PosLoyalty.removeRewardLine("90% on the cheapest product"),
|
||||
PosLoyalty.orderTotalIs("45.90"),
|
||||
PosLoyalty.enterCode("invalid_code"),
|
||||
Notification.has("invalid_code"),
|
||||
PosLoyalty.enterCode("1234"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-15.30"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
|
||||
// Use coupon but eventually remove the reward
|
||||
// applied programs:
|
||||
// - on cheapest product
|
||||
ProductScreen.addOrderline("Letter Tray", "4"),
|
||||
ProductScreen.addOrderline("Desk Organizer", "9"),
|
||||
PosLoyalty.hasRewardLine("90% on the cheapest product", "-4.59"),
|
||||
PosLoyalty.orderTotalIs("62.43"),
|
||||
PosLoyalty.enterCode("5678"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-15.30"),
|
||||
PosLoyalty.orderTotalIs("47.13"),
|
||||
PosLoyalty.removeRewardLine("Free Product"),
|
||||
PosLoyalty.orderTotalIs("62.43"),
|
||||
PosLoyalty.finalizeOrder("Cash", "90"),
|
||||
|
||||
// specific product discount
|
||||
// applied programs:
|
||||
// - on cheapest product
|
||||
// - on specific products
|
||||
ProductScreen.addOrderline("Magnetic Board", "10"), // 1.98
|
||||
ProductScreen.addOrderline("Desk Organizer", "3"), // 5.1
|
||||
ProductScreen.addOrderline("Letter Tray", "4"), // 4.8 tax 10%
|
||||
PosLoyalty.hasRewardLine("90% on the cheapest product", "-1.78"),
|
||||
PosLoyalty.orderTotalIs("54.44"),
|
||||
PosLoyalty.enterCode("promocode"),
|
||||
PosLoyalty.hasRewardLine("50% on specific products", "-16.66"), // 17.55 - 1.78*0.5
|
||||
PosLoyalty.orderTotalIs("37.78"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour2", {
|
||||
steps: () =>
|
||||
[
|
||||
// --- PoS Loyalty Tour Basic Part 2 ---
|
||||
// Using the coupons generated from PosLoyaltyTour1.
|
||||
|
||||
// Test that global discount and cheapest product discounts can be accumulated.
|
||||
// Applied programs:
|
||||
// - global discount
|
||||
// - on cheapest discount
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.addOrderline("Desk Organizer", "10"), // 5.1
|
||||
PosLoyalty.hasRewardLine("on the cheapest product", "-4.59"),
|
||||
ProductScreen.addOrderline("Letter Tray", "4"), // 4.8 tax 10%
|
||||
PosLoyalty.hasRewardLine("on the cheapest product", "-4.59"),
|
||||
PosLoyalty.enterCode("123456"),
|
||||
PosLoyalty.hasRewardLine("10% on your order", "-4.64"),
|
||||
PosLoyalty.hasRewardLine("10% on your order", "-2.11"),
|
||||
PosLoyalty.orderTotalIs("60.78"), //SUBTOTAL
|
||||
PosLoyalty.finalizeOrder("Cash", "70"),
|
||||
|
||||
// Scanning coupon twice.
|
||||
// Also apply global discount on top of free product to check if the
|
||||
// calculated discount is correct.
|
||||
// Applied programs:
|
||||
// - coupon program (free product)
|
||||
// - global discount
|
||||
// - on cheapest discount
|
||||
ProductScreen.addOrderline("Desk Organizer", "11"), // 5.1 per item
|
||||
PosLoyalty.hasRewardLine("90% on the cheapest product", "-4.59"),
|
||||
PosLoyalty.orderTotalIs("51.51"),
|
||||
// add global discount and the discount will be replaced
|
||||
PosLoyalty.enterCode("345678"),
|
||||
PosLoyalty.hasRewardLine("10% on your order", "-5.15"),
|
||||
// add free product coupon (for qty=11, free=4)
|
||||
// the discount should change after having free products
|
||||
// it should go back to cheapest discount as it is higher
|
||||
PosLoyalty.enterCode("5678"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-20.40"),
|
||||
PosLoyalty.hasRewardLine("90% on the cheapest product", "-4.59"),
|
||||
// set quantity to 18
|
||||
// free qty stays the same since the amount of points on the card only allows for 4 free products
|
||||
//TODO: The following step should works with ProductScreen.clickNumpad("⌫", "8"),
|
||||
ProductScreen.clickNumpad("⌫", "⌫", "1", "8"),
|
||||
PosLoyalty.hasRewardLine("10% on your order", "-6.68"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-20.40"),
|
||||
// scan the code again and check notification
|
||||
PosLoyalty.enterCode("5678"),
|
||||
PosLoyalty.orderTotalIs("60.13"),
|
||||
PosLoyalty.finalizeOrder("Cash", "65"),
|
||||
|
||||
// Specific products discount (with promocode) and free product (1357)
|
||||
// Applied programs:
|
||||
// - discount on specific products
|
||||
// - free product
|
||||
ProductScreen.addOrderline("Desk Organizer", "6"), // 5.1 per item
|
||||
PosLoyalty.hasRewardLine("on the cheapest product", "-4.59"),
|
||||
PosLoyalty.removeRewardLine("90% on the cheapest product"),
|
||||
PosLoyalty.enterCode("promocode"),
|
||||
PosLoyalty.hasRewardLine("50% on specific products", "-15.30"),
|
||||
PosLoyalty.enterCode("1357"),
|
||||
PosLoyalty.hasRewardLine("Free Product - Desk Organizer", "-10.20"),
|
||||
PosLoyalty.hasRewardLine("50% on specific products", "-10.20"),
|
||||
PosLoyalty.orderTotalIs("10.20"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
|
||||
// Check reset program
|
||||
// Enter two codes and reset the programs.
|
||||
// The codes should be checked afterwards. They should return to new.
|
||||
// Applied programs:
|
||||
// - cheapest product
|
||||
ProductScreen.addOrderline("Monitor Stand", "6"), // 3.19 per item
|
||||
PosLoyalty.enterCode("098765"),
|
||||
PosLoyalty.hasRewardLine("90% on the cheapest product", "-2.87"),
|
||||
PosLoyalty.hasRewardLine("10% on your order", "-1.63"),
|
||||
PosLoyalty.orderTotalIs("14.64"),
|
||||
PosLoyalty.removeRewardLine("90% on the cheapest product"),
|
||||
PosLoyalty.hasRewardLine("10% on your order", "-1.91"),
|
||||
PosLoyalty.orderTotalIs("17.23"),
|
||||
ProductScreen.clickControlButton("Reset Programs"),
|
||||
PosLoyalty.hasRewardLine("90% on the cheapest product", "-2.87"),
|
||||
PosLoyalty.orderTotalIs("16.27"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour3", {
|
||||
steps: () =>
|
||||
[
|
||||
// --- PoS Loyalty Tour Basic Part 3 ---
|
||||
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Promo Product"),
|
||||
PosLoyalty.orderTotalIs("34.50"),
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
PosLoyalty.hasRewardLine("100% on specific products", "25.00"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
PosLoyalty.hasRewardLine("100% on specific products", "15.00"),
|
||||
PosLoyalty.orderTotalIs("34.50"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
PosLoyalty.hasRewardLine("100% on specific products", "21.82"),
|
||||
PosLoyalty.hasRewardLine("100% on specific products", "18.18"),
|
||||
PosLoyalty.orderTotalIs("49.50"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour4", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.addOrderline("Test Product 1", "1"),
|
||||
ProductScreen.addOrderline("Test Product 2", "1"),
|
||||
ProductScreen.clickPriceList("Public Pricelist"),
|
||||
PosLoyalty.enterCode("abcda"),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
ProductScreen.clickPriceList("Test multi-currency"),
|
||||
PosLoyalty.orderTotalIs("0.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosCouponTour5", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
ProductScreen.addOrderline("Test Product 1", "1", "100"),
|
||||
PosLoyalty.pointsAwardedAre("115"),
|
||||
PosLoyalty.clickDiscountButton(),
|
||||
Dialog.confirm(),
|
||||
ProductScreen.totalAmountIs("92.00"),
|
||||
PosLoyalty.pointsAwardedAre("92"),
|
||||
Chrome.endTour(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
//transform the last tour to match the new format
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour6", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Partner"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product A"),
|
||||
PosLoyalty.checkAddedLoyaltyPoints("26.5"),
|
||||
ProductScreen.clickControlButton("Reward"),
|
||||
SelectionPopup.has("$ 1 per point on your order", { run: "click" }),
|
||||
ProductScreen.totalAmountIs("165.00"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
PosLoyalty.isLoyaltyPointsAvailable(),
|
||||
Utils.refresh(),
|
||||
ReceiptScreen.isShown(),
|
||||
PosLoyalty.isLoyaltyPointsAvailable(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour7", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.addOrderline("Test Product", "1"),
|
||||
PosLoyalty.orderTotalIs("100"),
|
||||
PosLoyalty.enterCode("abcda"),
|
||||
PosLoyalty.orderTotalIs("90"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour8", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.totalAmountIs("50.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltySpecificDiscountCategoryTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Product A", true, "1", "15.00"),
|
||||
PosLoyalty.orderTotalIs("15.00"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Product B", true, "1", "50.00"),
|
||||
PosLoyalty.orderTotalIs("40.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour9", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Partner"),
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.totalAmountIs("210.00"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
PosLoyalty.claimReward("$ 5"),
|
||||
ProductScreen.totalAmountIs("205.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour10", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Partner"),
|
||||
PosLoyalty.customerIs("AAA Partner"),
|
||||
ProductScreen.clickDisplayedProduct("Product Test"),
|
||||
ProductScreen.totalAmountIs("1.00"),
|
||||
ProductScreen.selectedOrderlineHas("Product Test", "1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
PosLoyalty.claimReward("Free Product B"),
|
||||
{
|
||||
content: `click on reward item`,
|
||||
trigger: `.selection-item:contains("Free Product B")`,
|
||||
run: "click",
|
||||
},
|
||||
PosLoyalty.hasRewardLine("Free Product B", "-1.00"),
|
||||
ProductScreen.totalAmountIs("1.00"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour11.1", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Partner"),
|
||||
PosLoyalty.customerIs("AAA Partner"),
|
||||
ProductScreen.addOrderline("Product Test", "3"),
|
||||
ProductScreen.totalAmountIs("150.00"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
PosLoyalty.finalizeOrder("Cash", "150"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour11.2", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAA Partner"),
|
||||
PosLoyalty.customerIs("AAA Partner"),
|
||||
ProductScreen.clickDisplayedProduct("Product Test"),
|
||||
ProductScreen.totalAmountIs("50.00"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
PosLoyalty.enterCode("123456"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
PosLoyalty.claimReward("Free Product"),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-3.00"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false),
|
||||
ProductScreen.totalAmountIs("50.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "50"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyMinAmountAndSpecificProductTour", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.selectedOrderlineHas("Product A", "1", "20.00"),
|
||||
PosLoyalty.orderTotalIs("20.00"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Product B"),
|
||||
ProductScreen.selectedOrderlineHas("Product B", "1", "30.00"),
|
||||
PosLoyalty.orderTotalIs("50.00"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Product A"),
|
||||
ProductScreen.selectedOrderlineHas("Product A", "2", "40.00"),
|
||||
PosLoyalty.orderTotalIs("66.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyTour12", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Free Product A", "2"),
|
||||
ProductScreen.clickDisplayedProduct("Free Product A"),
|
||||
ProductScreen.totalAmountIs("2.00"),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-1.00"),
|
||||
ProductScreen.addOrderline("Free Product B", "2"),
|
||||
ProductScreen.clickDisplayedProduct("Free Product B"),
|
||||
ProductScreen.totalAmountIs("12.00"),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-5.00"),
|
||||
ProductScreen.clickDisplayedProduct("Free Product B"),
|
||||
ProductScreen.clickDisplayedProduct("Free Product B"),
|
||||
ProductScreen.clickDisplayedProduct("Free Product B"),
|
||||
ProductScreen.selectedOrderlineHas("Free Product B", "6"),
|
||||
ProductScreen.totalAmountIs("22.00"),
|
||||
PosLoyalty.hasRewardLine("Free Product", "-10.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
function createOrderCoupon(totalAmount, couponName, couponAmount, loyaltyPoints) {
|
||||
return [
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
ProductScreen.addOrderline("product_a", "1"),
|
||||
ProductScreen.addOrderline("product_b", "1"),
|
||||
PosLoyalty.enterCode("promocode"),
|
||||
PosLoyalty.hasRewardLine(`${couponName}`, `${couponAmount}`),
|
||||
PosLoyalty.orderTotalIs(`${totalAmount}`),
|
||||
PosLoyalty.pointsAwardedAre(`${loyaltyPoints}`),
|
||||
PosLoyalty.finalizeOrder("Cash", `${totalAmount}`),
|
||||
].flat();
|
||||
}
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyPointsDiscountNoDomainProgramNoDomain", {
|
||||
steps: () => [createOrderCoupon("135.00", "10% on your order", "-15.00", "135")].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyPointsDiscountNoDomainProgramDomain", {
|
||||
steps: () => [createOrderCoupon("135.00", "10% on your order", "-15.00", "100")].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyPointsDiscountWithDomainProgramDomain", {
|
||||
steps: () => [createOrderCoupon("140.00", "10% on food", "-10.00", "90")].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyPointsGlobalDiscountProgramNoDomain", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("product_a", "1"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
PosLoyalty.hasRewardLine("10% on your order", "-10.00"),
|
||||
PosLoyalty.orderTotalIs("90"),
|
||||
PosLoyalty.pointsAwardedAre("90"),
|
||||
PosLoyalty.finalizeOrder("Cash", "90"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("ChangeRewardValueWithLanguage", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Desk Organizer"),
|
||||
ProductScreen.selectedOrderlineHas("Desk Organizer", "1", "5.10"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Partner Test 1"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true, true),
|
||||
PosLoyalty.claimReward("$ 2 on your order"),
|
||||
PosLoyalty.hasRewardLine("$ 2 on your order", "-2.00"),
|
||||
PosLoyalty.orderTotalIs("3.10"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyArchivedRewardProductsInactive", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("Test Product A"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false, true),
|
||||
ProductScreen.selectedOrderlineHas("Test Product A", "1", "100.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "100"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyArchivedRewardProductsActive", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.clickDisplayedProduct("Test Product A"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
PosLoyalty.isRewardButtonHighlighted(true),
|
||||
ProductScreen.selectedOrderlineHas("Test Product A", "1", "100.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "100"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("CustomerLoyaltyPointsDisplayed", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickDisplayedProduct("product_a"),
|
||||
ProductScreen.selectedOrderlineHas("product_a", "1", "100.00"),
|
||||
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("John Doe"),
|
||||
|
||||
PosLoyalty.orderTotalIs("100.00"),
|
||||
PosLoyalty.pointsAwardedAre("100"),
|
||||
PosLoyalty.finalizeOrder("Cash", "100.00"),
|
||||
|
||||
PosLoyalty.checkPartnerPoints("John Doe", "100.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyalty2DiscountsSpecificGlobal", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
|
||||
ProductScreen.addOrderline("Test Product A", "5"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product B"),
|
||||
PosLoyalty.hasRewardLine("10% on your order", "-3.00"),
|
||||
PosLoyalty.hasRewardLine("10% on Test Product B", "-0.45"),
|
||||
PosLoyalty.finalizeOrder("Cash", "100"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltySpecificProductDiscountWithGlobalDiscount", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Product A", "1"),
|
||||
PosLoyalty.hasRewardLine("$ 40 on Product A", "-40.00"),
|
||||
PosLoyalty.clickDiscountButton(),
|
||||
Dialog.confirm(),
|
||||
PosLoyalty.hasRewardLine("$ 40 on Product A", "-40.00"),
|
||||
PosLoyalty.orderTotalIs("20.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosRewardProductScan", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
scan_barcode("95412427100283"),
|
||||
ProductScreen.selectedOrderlineHas("product_a", "1", "1,150.00"),
|
||||
PosLoyalty.hasRewardLine("50% on your order", "-575.00"),
|
||||
PosLoyalty.orderTotalIs("575.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "575.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosRewardProductScanGS1", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
scan_barcode("0195412427100283"),
|
||||
ProductScreen.selectedOrderlineHas("product_a", "1", "1,150.00"),
|
||||
PosLoyalty.hasRewardLine("50% on your order", "-575.00"),
|
||||
PosLoyalty.orderTotalIs("575.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "575.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyPromocodePricelist", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Test Product 1", "1"),
|
||||
PosLoyalty.enterCode("hellopromo"),
|
||||
PosLoyalty.orderTotalIs("25.87"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("RefundRulesProduct", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("product_a"),
|
||||
PosLoyalty.finalizeOrder("Cash", "1000"),
|
||||
ProductScreen.isShown(),
|
||||
...ProductScreen.clickRefund(),
|
||||
TicketScreen.filterIs("Paid"),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_two_variant_same_discount", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Sofa"),
|
||||
Chrome.clickBtn("Add"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_settle_dont_give_points_again", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
PartnerList.settleCustomerAccount("AAA Partner", "10.00", "TSJ/"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_refund_does_not_decrease_points", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("Refunding Guy", true),
|
||||
ProductScreen.clickDisplayedProduct("Refund Product"),
|
||||
ProductScreen.clickControlButton("Reward"),
|
||||
SelectionPopup.has("$ 1 per point on your order", { run: "click" }),
|
||||
PosLoyalty.finalizeOrder("Cash", "200"),
|
||||
ProductScreen.clickRefund(),
|
||||
TicketScreen.selectOrder("001"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
ProductScreen.clickLine("$ 1 per point on your order"),
|
||||
ProductScreen.clickNumpad("1"),
|
||||
TicketScreen.confirmRefund(),
|
||||
PaymentScreen.totalIs("-200.00"),
|
||||
PaymentScreen.clickPaymentMethod("Cash"),
|
||||
PaymentScreen.clickValidate(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_discount_after_unknown_scan", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Test Product A", "1"),
|
||||
scan_barcode("00998877665544332211"), //should be unknown
|
||||
PosLoyalty.hasRewardLine("10% on Test Product A", "-0.50"),
|
||||
ProductScreen.totalAmountIs("4.50"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_scan_loyalty_card_select_customer", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
scan_barcode("0444-e050-4548"),
|
||||
ProductScreen.customerIsSelected("AAA Test Partner"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_min_qty_points_awarded", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AA Partner"),
|
||||
ProductScreen.clickDisplayedProduct("Whiteboard Pen"),
|
||||
PosLoyalty.claimReward("Free Product"),
|
||||
PosLoyalty.pointsTotalIs("90"),
|
||||
PosLoyalty.orderTotalIs("0.0"),
|
||||
PosLoyalty.finalizeOrder("Cash", "0.0"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_confirm_coupon_programs_one_by_one", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
{
|
||||
trigger: "body",
|
||||
content: "Create fake orders",
|
||||
run: async () => {
|
||||
// Create 5 orders that will be synced one by one
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const order = posmodel.createNewOrder();
|
||||
const product = posmodel.models["product.template"].find(
|
||||
(p) => p.name === "Desk Pad"
|
||||
);
|
||||
const pm = posmodel.models["pos.payment.method"].getFirst();
|
||||
const program = posmodel.models["loyalty.program"].find(
|
||||
(p) => p.program_type === "gift_card"
|
||||
);
|
||||
|
||||
await posmodel.addLineToOrder({ product_tmpl_id: product }, order);
|
||||
posmodel.addPendingOrder([order.id]);
|
||||
order.addPaymentline(pm);
|
||||
order.state = "paid";
|
||||
|
||||
// Create fake coupon point changes to simulate coupons to be confirmed
|
||||
order.uiState.couponPointChanges = [
|
||||
{
|
||||
points: 124.2,
|
||||
program_id: program.id,
|
||||
coupon_id: -(i + 1),
|
||||
barcode: "",
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
},
|
||||
// Create one more order to be able to trigger the sync from the UI
|
||||
ProductScreen.clickDisplayedProduct("Desk Pad"),
|
||||
ProductScreen.clickPayButton(),
|
||||
PaymentScreen.clickPaymentMethod("Bank"),
|
||||
PaymentScreen.clickValidate(),
|
||||
ReceiptScreen.isShown(),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_order_reward_product_tax_included_included", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Product Include", "1"),
|
||||
PosLoyalty.enterCode("hellopromo"),
|
||||
PosLoyalty.hasRewardLine("$ 10 on your order", "-10.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_order_reward_product_tax_included_excluded", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.addOrderline("Product Include", "1"),
|
||||
PosLoyalty.enterCode("hellopromo"),
|
||||
PosLoyalty.hasRewardLine("$ 10 on your order", "-10.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_specific_reward_product_tax_included_included", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.addOrderline("Product Include", "1"),
|
||||
PosLoyalty.enterCode("hellopromo"),
|
||||
PosLoyalty.hasRewardLine("$ 10 on Product Include", "-10.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_specific_reward_product_tax_included_excluded", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.addOrderline("Product Include", "1"),
|
||||
PosLoyalty.enterCode("hellopromo"),
|
||||
PosLoyalty.hasRewardLine("$ 10 on Product Include", "-10.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_loyalty_is_not_processed_for_draft_order", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickPartnerButton(),
|
||||
ProductScreen.clickCustomer("AAAA"),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "1", "100"),
|
||||
PosLoyalty.pointsAwardedAre("100"),
|
||||
PosLoyalty.pointsTotalIs("150"),
|
||||
ProductScreen.saveOrder(),
|
||||
ProductScreen.selectFloatingOrder(0),
|
||||
PosLoyalty.pointsAwardedAre("100"),
|
||||
PosLoyalty.pointsTotalIs("150"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_race_conditions_update_program", {
|
||||
steps: () =>
|
||||
[
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
ProductScreen.clickDisplayedProduct("Test Product"),
|
||||
PosLoyalty.orderTotalIs("34.87"),
|
||||
{
|
||||
trigger: "body",
|
||||
run: async () => {
|
||||
// Check the number of lines in the order
|
||||
const line_count = document.querySelectorAll(".orderline").length;
|
||||
if (line_count !== 11) {
|
||||
throw new Error(`Expected 11 orderlines, found ${line_count}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import * as PosLoyalty from "@pos_loyalty/../tests/tours/utils/pos_loyalty_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyValidity1", {
|
||||
steps: () =>
|
||||
[
|
||||
// First tour should not get any automatic rewards
|
||||
|
||||
Chrome.startPoS(),
|
||||
Dialog.confirm("Open Register"),
|
||||
|
||||
// Not valid -> date
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "5"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false, true),
|
||||
PosLoyalty.orderTotalIs("16.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "16"),
|
||||
].flat(),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("PosLoyaltyValidity2", {
|
||||
steps: () =>
|
||||
[
|
||||
// Second tour
|
||||
// Valid
|
||||
Chrome.startPoS(),
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "5"),
|
||||
PosLoyalty.hasRewardLine("90% on the cheapest product", "-2.88"),
|
||||
PosLoyalty.finalizeOrder("Cash", "20"),
|
||||
|
||||
// Not valid -> usage
|
||||
ProductScreen.addOrderline("Whiteboard Pen", "5"),
|
||||
PosLoyalty.isRewardButtonHighlighted(false, true),
|
||||
PosLoyalty.orderTotalIs("16.00"),
|
||||
PosLoyalty.finalizeOrder("Cash", "16.00"),
|
||||
].flat(),
|
||||
});
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
import * as Order from "@point_of_sale/../tests/generic_helpers/order_widget_util";
|
||||
import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util";
|
||||
import * as TextInputPopup from "@point_of_sale/../tests/generic_helpers/text_input_popup_util";
|
||||
import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util";
|
||||
import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util";
|
||||
import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util";
|
||||
import { negate } from "@point_of_sale/../tests/generic_helpers/utils";
|
||||
import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util";
|
||||
|
||||
export function selectRewardLine(rewardName) {
|
||||
return [
|
||||
...Order.hasLine({
|
||||
withClass: ".fst-italic",
|
||||
withoutClass: ".selected",
|
||||
run: "click",
|
||||
productName: rewardName,
|
||||
}),
|
||||
...Order.hasLine({
|
||||
withClass: ".selected.fst-italic",
|
||||
productName: rewardName,
|
||||
}),
|
||||
];
|
||||
}
|
||||
export function enterCode(code) {
|
||||
return [
|
||||
...ProductScreen.clickControlButton("Enter Code"),
|
||||
TextInputPopup.inputText(code),
|
||||
Dialog.confirm(),
|
||||
];
|
||||
}
|
||||
export function clickEWalletButton(text = "eWallet") {
|
||||
return [{ trigger: ProductScreen.controlButtonTrigger(text), run: "click" }];
|
||||
}
|
||||
export function claimReward(rewardName) {
|
||||
return [
|
||||
...ProductScreen.clickControlButton("Reward"),
|
||||
{
|
||||
// There should be description because a program always has a name.
|
||||
trigger: ".selection-item span:nth-child(2)",
|
||||
},
|
||||
{
|
||||
content: "select reward",
|
||||
trigger: `.selection-item:contains("${rewardName}")`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function unselectPartner() {
|
||||
return [{ trigger: ".unselect-tag", run: "click" }];
|
||||
}
|
||||
export function clickDiscountButton() {
|
||||
return [
|
||||
...ProductScreen.clickControlButtonMore(),
|
||||
{
|
||||
content: "click discount button",
|
||||
trigger: ".js_discount",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function hasRewardLine(rewardName, amount, qty) {
|
||||
return Order.hasLine({
|
||||
withClass: ".fst-italic",
|
||||
productName: rewardName,
|
||||
price: amount,
|
||||
quantity: qty,
|
||||
});
|
||||
}
|
||||
export function orderTotalIs(total_str) {
|
||||
return [...Order.hasTotal(total_str)];
|
||||
}
|
||||
export function isRewardButtonHighlighted(isHighlighted, closeModal = true) {
|
||||
const steps = [
|
||||
...ProductScreen.clickControlButtonMore(),
|
||||
{
|
||||
trigger: isHighlighted
|
||||
? '.control-buttons button.highlight:contains("Reward")'
|
||||
: '.control-buttons button.disabled:contains("Reward")',
|
||||
},
|
||||
];
|
||||
if (closeModal) {
|
||||
steps.push({
|
||||
content: "Close modal after checked if reward button is highlighted",
|
||||
trigger: ".modal header .btn-close",
|
||||
run: "click",
|
||||
});
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
export function eWalletButtonState({ highlighted, text = "eWallet", click = false }) {
|
||||
const step = {
|
||||
trigger: highlighted
|
||||
? `.control-buttons button.highlight:contains("${text}")`
|
||||
: `.control-buttons button.disabled:contains("${text}")`,
|
||||
};
|
||||
if (click) {
|
||||
step.run = "click";
|
||||
}
|
||||
const steps = [...ProductScreen.clickControlButtonMore(), step];
|
||||
if (!click) {
|
||||
steps.push({
|
||||
//Previous step is just a check. No need to keep modal openened
|
||||
trigger: ".modal header .btn-close",
|
||||
run: "click",
|
||||
});
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
export function customerIs(name) {
|
||||
return [
|
||||
{
|
||||
trigger: `.product-screen .set-partner:contains("${name}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
export function isPointsDisplayed(isDisplayed) {
|
||||
return [
|
||||
{
|
||||
trigger: isDisplayed
|
||||
? ".loyalty-points-title"
|
||||
: "body:not(:has(.loyalty-points-title))",
|
||||
},
|
||||
];
|
||||
}
|
||||
export function pointsAwardedAre(points_str) {
|
||||
return [
|
||||
{
|
||||
content: "loyalty points awarded " + points_str,
|
||||
trigger: '.loyalty-points-won:contains("' + points_str + '")',
|
||||
},
|
||||
];
|
||||
}
|
||||
export function pointsTotalIs(points_str) {
|
||||
return [
|
||||
{
|
||||
content: "loyalty points awarded " + points_str,
|
||||
trigger: '.loyalty-points-totaltext-end:contains("' + points_str + '")',
|
||||
},
|
||||
];
|
||||
}
|
||||
export function finalizeOrder(paymentMethod, amount) {
|
||||
return [
|
||||
...ProductScreen.clickPayButton(),
|
||||
...PaymentScreen.clickPaymentMethod(paymentMethod),
|
||||
...PaymentScreen.clickNumpad([...amount].join(" ")),
|
||||
...PaymentScreen.clickValidate(),
|
||||
...ReceiptScreen.clickNextOrder(),
|
||||
];
|
||||
}
|
||||
export function removeRewardLine(name) {
|
||||
return [selectRewardLine(name), ProductScreen.clickNumpad("⌫"), Dialog.confirm()].flat();
|
||||
}
|
||||
|
||||
export function checkAddedLoyaltyPoints(points) {
|
||||
return [
|
||||
{
|
||||
trigger: `.loyalty-points-won:contains("${points}")`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function useExistingLoyaltyCard(code, valid = true) {
|
||||
const steps = [
|
||||
{
|
||||
trigger: `a:contains("Sell physical gift card?")`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: `Input code '${code}'`,
|
||||
trigger: `input[id="code"]`,
|
||||
run: `edit ${code}`,
|
||||
},
|
||||
{
|
||||
content: "Not loading",
|
||||
trigger: negate(".gift-card-loading"),
|
||||
},
|
||||
];
|
||||
|
||||
if (!valid) {
|
||||
steps.push(Dialog.confirm("Ok"));
|
||||
steps.push(Dialog.cancel());
|
||||
steps.push({
|
||||
trigger: `a:contains("Sell physical gift card?")`,
|
||||
run: () => {},
|
||||
});
|
||||
} else {
|
||||
steps.push(...Chrome.waitRequest());
|
||||
steps.push(Dialog.confirm());
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
export function createManualGiftCard(code, amount, date = false) {
|
||||
const steps = [
|
||||
{
|
||||
trigger: `a:contains("Sell physical gift card?")`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: `Input code '${code}'`,
|
||||
trigger: `input[id="code"]`,
|
||||
run: `edit ${code}`,
|
||||
},
|
||||
{
|
||||
content: `Input amount '${amount}'`,
|
||||
trigger: `input[id="amount"]`,
|
||||
run: `edit ${amount}`,
|
||||
},
|
||||
];
|
||||
if (date !== false) {
|
||||
steps.push({
|
||||
content: `Input date '${date}'`,
|
||||
trigger: `.modal input.o_datetime_input.cursor-pointer.form-control.form-control-lg`,
|
||||
run: `edit ${date}`,
|
||||
});
|
||||
}
|
||||
steps.push({
|
||||
trigger: `.btn-primary:contains("Add Balance")`,
|
||||
run: "click",
|
||||
});
|
||||
return steps;
|
||||
}
|
||||
|
||||
export function clickGiftCardProgram(name) {
|
||||
return [
|
||||
{
|
||||
content: `Click gift card program '${name}'`,
|
||||
trigger: `button.selection-item:has(span:contains("${name}"))`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function clickPhysicalGiftCard(code = "Sell physical gift card?") {
|
||||
return [
|
||||
{
|
||||
trigger: `ul.info-list .text-wrap:contains("${code}")`,
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function checkPartnerPoints(name, points) {
|
||||
return [
|
||||
...ProductScreen.clickPartnerButton(),
|
||||
{
|
||||
content: `Check '${name}' has ${points} Loyalty Points`,
|
||||
trigger: `.partner-list .partner-line:contains(${name}) .partner-line-balance:contains(${points} Loyalty Point(s))`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function isMoreControlButtonActive(active) {
|
||||
return {
|
||||
content: "More control button is " + (active ? "active" : "not active"),
|
||||
trigger: active
|
||||
? ".control-buttons .more-btn.active"
|
||||
: ".control-buttons:not(:has(.more-btn.active))",
|
||||
};
|
||||
}
|
||||
export function isLoyaltyPointsAvailable() {
|
||||
return {
|
||||
content: "Loyalty Points are visible on the receipt",
|
||||
trigger: ".pos-receipt .loyalty",
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { getFilledOrder, setupPosEnv } from "@point_of_sale/../tests/unit/utils";
|
||||
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
|
||||
import { ManageGiftCardPopup } from "@pos_loyalty/app/components/popups/manage_giftcard_popup/manage_giftcard_popup";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("addBalance", async () => {
|
||||
const store = await setupPosEnv();
|
||||
|
||||
// Freeze current date so luxon.DateTime.now() is fixed
|
||||
mockDate("2025-01-01");
|
||||
|
||||
let payloadResult = null;
|
||||
|
||||
const order = await getFilledOrder(store);
|
||||
const popup = await mountWithCleanup(ManageGiftCardPopup, {
|
||||
props: {
|
||||
line: order.lines[0],
|
||||
title: "Sell/Manage physical gift card",
|
||||
getPayload: (code, amount, expDate) => {
|
||||
payloadResult = { code, amount, expDate };
|
||||
},
|
||||
close: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
popup.state.inputValue = "";
|
||||
popup.state.amountValue = "";
|
||||
const valid = popup.validateCode();
|
||||
|
||||
expect(valid).toBe(false);
|
||||
expect(popup.state.error).toBe(true);
|
||||
|
||||
popup.state.inputValue = "101";
|
||||
popup.state.amountValue = "100";
|
||||
popup.state.error = false;
|
||||
popup.state.amountError = false;
|
||||
|
||||
await popup.addBalance();
|
||||
|
||||
expect(payloadResult.code).toBe("101");
|
||||
expect(payloadResult.amount).toBe(100);
|
||||
// expiration is +1 year
|
||||
expect(payloadResult.expDate).toBe("2026-01-01");
|
||||
});
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
|
||||
import { ControlButtons } from "@point_of_sale/app/screens/product_screen/control_buttons/control_buttons";
|
||||
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
|
||||
import { addProductLineToOrder } from "@pos_loyalty/../tests/unit/utils";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("_applyReward", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
// Get card #1 - belongs to a loyalty-type program
|
||||
const card = models["loyalty.card"].get(1);
|
||||
// Get reward #2 - belongs to the same loyalty program, type = discount reward
|
||||
const reward = models["loyalty.reward"].get(2);
|
||||
|
||||
await addProductLineToOrder(store, order);
|
||||
|
||||
// Total quantity in the order
|
||||
const potentialQty = order.getOrderlines().reduce((acc, line) => acc + line.qty, 0);
|
||||
|
||||
const component = await mountWithCleanup(ControlButtons, {});
|
||||
|
||||
const result = await component._applyReward(reward, card.id, potentialQty);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("getPotentialRewards", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
// Get loyalty program #1 - type = loyalty
|
||||
const loyaltyProgram = models["loyalty.program"].get(1);
|
||||
// Get card #1 - linked to loyalty program #1
|
||||
const card = models["loyalty.card"].get(1);
|
||||
|
||||
await addProductLineToOrder(store, order);
|
||||
order._code_activated_coupon_ids = [card];
|
||||
|
||||
const component = await mountWithCleanup(ControlButtons, {});
|
||||
|
||||
const rewards = component.getPotentialRewards();
|
||||
const reward = rewards[0].reward;
|
||||
|
||||
expect(reward).toEqual(models["loyalty.reward"].get(1));
|
||||
expect(reward.program_id).toEqual(loyaltyProgram);
|
||||
});
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { OrderSummary } from "@point_of_sale/app/screens/product_screen/order_summary/order_summary";
|
||||
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
|
||||
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
|
||||
import { addProductLineToOrder } from "@pos_loyalty/../tests/unit/utils";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("_updateGiftCardOrderline", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
const product = models["product.product"].get(1);
|
||||
// Program #3 - loyalty program for gift cards
|
||||
const program = models["loyalty.program"].get(3);
|
||||
// Card #3 - gift card which program type is gift_card
|
||||
const card = models["loyalty.card"].get(3);
|
||||
|
||||
await addProductLineToOrder(store, order);
|
||||
|
||||
const points = product.lst_price;
|
||||
|
||||
order.uiState.couponPointChanges[card.id] = {
|
||||
coupon_id: card.id,
|
||||
program_id: program.id,
|
||||
product_id: product.id,
|
||||
points: points,
|
||||
manual: false,
|
||||
};
|
||||
|
||||
const component = await mountWithCleanup(OrderSummary, {});
|
||||
|
||||
await component._updateGiftCardOrderline("ABC123", points);
|
||||
|
||||
const updatedLine = order.getSelectedOrderline();
|
||||
|
||||
expect(updatedLine.gift_code).toBe("ABC123");
|
||||
expect(updatedLine.product_id.id).toBe(product.id);
|
||||
expect(updatedLine.getQuantity()).toBe(1);
|
||||
expect(order.uiState.couponPointChanges[card.id]).toBe(undefined);
|
||||
});
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
|
||||
import { PartnerLine } from "@point_of_sale/app/screens/partner_list/partner_line/partner_line";
|
||||
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("_getLoyaltyPointsRepr", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
|
||||
const partner = models["res.partner"].get(1);
|
||||
// Get first 3 loyalty cards and map them with program
|
||||
const loyaltyCards = models["loyalty.card"]
|
||||
.getAll()
|
||||
.slice(0, 3)
|
||||
.map((element) => ({
|
||||
id: element.id,
|
||||
points: element.points,
|
||||
partner_id: partner.id,
|
||||
program_id: models["loyalty.program"].get(element.id),
|
||||
}));
|
||||
|
||||
const component = await mountWithCleanup(PartnerLine, {
|
||||
props: {
|
||||
partner,
|
||||
close: () => {},
|
||||
isSelected: false,
|
||||
isBalanceDisplayed: true,
|
||||
onClickEdit: () => {},
|
||||
onClickUnselect: () => {},
|
||||
onClickPartner: () => {},
|
||||
onClickOrders: () => {},
|
||||
},
|
||||
env: {
|
||||
...store.env,
|
||||
utils: {
|
||||
formatCurrency: (val) => `$${val.toFixed(2)}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const results = loyaltyCards.map((card) => component._getLoyaltyPointsRepr(card));
|
||||
|
||||
expect(results[0]).toBe("10.00 Points");
|
||||
expect(results[1]).toBe("E-Wallet Program: $25.00");
|
||||
expect(results[2]).toMatch("15.00 Gift Card Points");
|
||||
});
|
||||
|
|
@ -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]);
|
||||
|
|
@ -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]);
|
||||
|
|
@ -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]);
|
||||
|
|
@ -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]);
|
||||
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
@ -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",
|
||||
];
|
||||
},
|
||||
});
|
||||
|
|
@ -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",
|
||||
];
|
||||
},
|
||||
});
|
||||
|
|
@ -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"];
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
import { test, describe, expect } from "@odoo/hoot";
|
||||
import { tick } from "@odoo/hoot-mock";
|
||||
import { setupPosEnv, getFilledOrder } from "@point_of_sale/../tests/unit/utils";
|
||||
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
|
||||
import { addProductLineToOrder } from "@pos_loyalty/../tests/unit/utils";
|
||||
|
||||
definePosModels();
|
||||
|
||||
const { DateTime } = luxon;
|
||||
|
||||
describe("pos.order - loyalty", () => {
|
||||
test("_getIgnoredProductIdsTotalDiscount", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = store.addNewOrder();
|
||||
|
||||
const ignoredProductIds = order._getIgnoredProductIdsTotalDiscount();
|
||||
|
||||
expect(ignoredProductIds.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("getOrderlines, _get_reward_lines and _get_regular_order_lines", async () => {
|
||||
const store = await setupPosEnv();
|
||||
|
||||
const order = await getFilledOrder(store);
|
||||
const [line1, line2] = order.getOrderlines();
|
||||
line1.update({ is_reward_line: true });
|
||||
line2.update({ is_reward_line: false, refunded_orderline_id: 123 });
|
||||
|
||||
// Verify getOrderlines method
|
||||
const orderedLines = order.getOrderlines();
|
||||
|
||||
expect(orderedLines[0]).toBe(line2);
|
||||
expect(orderedLines[1]).toBe(line1);
|
||||
expect(orderedLines[0].is_reward_line).toBe(false);
|
||||
expect(orderedLines[1].is_reward_line).toBe(true);
|
||||
expect(order.getLastOrderline()).toBe(line2);
|
||||
|
||||
// Verify _get_reward_lines method
|
||||
const rewardLines = order._get_reward_lines();
|
||||
|
||||
expect(rewardLines).toEqual([line1]);
|
||||
expect(rewardLines[0].is_reward_line).toBe(true);
|
||||
|
||||
// Verify _get_regular_order_lines
|
||||
const regularLine = await addProductLineToOrder(store, order);
|
||||
|
||||
expect(order.getOrderlines().length).toBe(3);
|
||||
|
||||
const regularLines = order._get_regular_order_lines();
|
||||
|
||||
expect(regularLines.length).toBe(2);
|
||||
expect(regularLines[1].id).toBe(regularLine.id);
|
||||
});
|
||||
|
||||
test("setPricelist", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
const pricelist2 = models["product.pricelist"].get(2);
|
||||
|
||||
order.uiState.couponPointChanges = {
|
||||
key1: { program_id: 1, points: 100 },
|
||||
key2: { program_id: 2, points: 50 },
|
||||
};
|
||||
|
||||
order.setPricelist(pricelist2);
|
||||
|
||||
const remainingKeys = Object.keys(order.uiState.couponPointChanges);
|
||||
expect(remainingKeys.length).toBe(1);
|
||||
expect(order.uiState.couponPointChanges[remainingKeys[0]].program_id).toBe(2);
|
||||
});
|
||||
|
||||
test("_resetPrograms", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = store.addNewOrder();
|
||||
|
||||
order.uiState.disabledRewards = new Set(["reward1"]);
|
||||
order.uiState.codeActivatedProgramRules = ["rule1"];
|
||||
order.uiState.couponPointChanges = { key1: { points: 100 } };
|
||||
|
||||
await addProductLineToOrder(store, order, {
|
||||
is_reward_line: true,
|
||||
});
|
||||
|
||||
order._resetPrograms();
|
||||
|
||||
expect(order.uiState.disabledRewards.size).toBeEmpty();
|
||||
expect(order.uiState.codeActivatedProgramRules.length).toBeEmpty();
|
||||
expect(order.uiState.couponPointChanges).toMatchObject({});
|
||||
});
|
||||
|
||||
test("_programIsApplicable", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
// Get loyalty program #1 - type = "ewallet"
|
||||
const program = models["loyalty.program"].get(1);
|
||||
|
||||
expect(order._programIsApplicable(program)).toBe(true);
|
||||
|
||||
program.partner_id = false;
|
||||
program.is_nominative = true;
|
||||
|
||||
expect(order._programIsApplicable(program)).toBe(false);
|
||||
});
|
||||
|
||||
test("_getRealCouponPoints", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
// Get loyalty card #1 which program_id = 1 (loyalty)
|
||||
const card = models["loyalty.card"].get(1);
|
||||
|
||||
order.uiState.couponPointChanges = {
|
||||
1: {
|
||||
coupon_id: 1,
|
||||
program_id: 1,
|
||||
points: 25,
|
||||
},
|
||||
};
|
||||
|
||||
await addProductLineToOrder(store, order, {
|
||||
is_reward_line: true,
|
||||
coupon_id: card,
|
||||
points_cost: 5,
|
||||
});
|
||||
|
||||
expect(order._getRealCouponPoints(card.id)).toBe(30);
|
||||
});
|
||||
|
||||
test("processGiftCard", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
// Get loyalty program #3 - type = "gift_card"
|
||||
const giftProgram = models["loyalty.program"].get(3);
|
||||
|
||||
const line = await addProductLineToOrder(store, order, {
|
||||
price_unit: 10,
|
||||
eWalletGiftCardProgram: giftProgram,
|
||||
});
|
||||
|
||||
order.selected_orderline = line;
|
||||
|
||||
const expirationDate = DateTime.now().plus({ days: 1 }).toISODate();
|
||||
order.processGiftCard("GIFT9999", 100, expirationDate);
|
||||
|
||||
const couponChanges = Object.values(order.uiState.couponPointChanges);
|
||||
expect(couponChanges.length).toBe(1);
|
||||
expect(couponChanges[0].code).toBe("GIFT9999");
|
||||
expect(couponChanges[0].points).toBe(100);
|
||||
expect(couponChanges[0].expiration_date).toBe(expirationDate);
|
||||
expect(couponChanges[0].manual).toBe(true);
|
||||
});
|
||||
|
||||
test("_getDiscountableOnOrder", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
await addProductLineToOrder(store, order, {
|
||||
qty: 2,
|
||||
});
|
||||
|
||||
await addProductLineToOrder(store, order, {
|
||||
price_unit: 5,
|
||||
});
|
||||
|
||||
// Get loyalty reward #1 - type = "discount"
|
||||
const reward = models["loyalty.reward"].get(1);
|
||||
|
||||
const result = order._getDiscountableOnOrder(reward);
|
||||
expect(result.discountable).toBe(25);
|
||||
});
|
||||
|
||||
test("_computeNItems", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
|
||||
const order = await getFilledOrder(store);
|
||||
|
||||
// Get loyalty rule #1 - which program_id = 1 (loyalty)
|
||||
const rule = models["loyalty.rule"].get(1);
|
||||
|
||||
expect(order.getOrderlines().length).toBe(2);
|
||||
expect(order._computeNItems(rule)).toBe(5);
|
||||
});
|
||||
|
||||
test("_canGenerateRewards", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
await addProductLineToOrder(store, order, {
|
||||
qty: 5,
|
||||
});
|
||||
|
||||
// Get loyalty program #2 - type = "ewallet"
|
||||
const program = models["loyalty.program"].get(2);
|
||||
|
||||
expect(order._canGenerateRewards(program, 50, 50)).toBe(true);
|
||||
expect(order._canGenerateRewards(program, 30, 30)).toBe(false);
|
||||
});
|
||||
|
||||
test("isProgramsResettable", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = store.addNewOrder();
|
||||
|
||||
expect(order.isProgramsResettable()).toBe(false);
|
||||
|
||||
order.uiState.disabledRewards = [...new Set(["RULE1"])];
|
||||
expect(order.isProgramsResettable()).toBe(true);
|
||||
|
||||
order.uiState.disabledRewards = new Set();
|
||||
order.uiState.codeActivatedProgramRules.push("RULE2");
|
||||
expect(order.isProgramsResettable()).toBe(true);
|
||||
|
||||
order.uiState.codeActivatedProgramRules = [];
|
||||
order.uiState.couponPointChanges = { key1: { points: 10 } };
|
||||
expect(order.isProgramsResettable()).toBe(true);
|
||||
});
|
||||
|
||||
test("removeOrderline", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
// Get loyalty reward #1 - type = "discount"
|
||||
const reward = models["loyalty.reward"].get(1);
|
||||
// Get loyalty card #1 - which program_id = 1 (loyalty)
|
||||
const coupon = models["loyalty.card"].get(1);
|
||||
|
||||
const rewardLine = await addProductLineToOrder(store, order, {
|
||||
is_reward_line: true,
|
||||
reward_id: reward,
|
||||
coupon_id: coupon,
|
||||
reward_identifier_code: "ABC123",
|
||||
});
|
||||
|
||||
const normalLine = await addProductLineToOrder(store, order, {
|
||||
price_unit: 20,
|
||||
is_reward_line: false,
|
||||
});
|
||||
|
||||
expect(order.getOrderlines().length).toBe(2);
|
||||
|
||||
const result = order.removeOrderline(rewardLine);
|
||||
expect(result).toBe(true);
|
||||
expect(order.getOrderlines().length).toBe(1);
|
||||
|
||||
const remainingLines = order.getOrderlines();
|
||||
expect(remainingLines.length).toBe(1);
|
||||
expect(remainingLines[0].id).toBe(normalLine.id);
|
||||
expect(remainingLines[0].is_reward_line).toBe(false);
|
||||
});
|
||||
|
||||
test("isSaleDisallowed", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
// Get loyalty program #3 - type = "gift_card"
|
||||
const giftProgram = models["loyalty.program"].get(3);
|
||||
|
||||
const result = order.isSaleDisallowed({}, { eWalletGiftCardProgram: giftProgram });
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test("setPartner and getLoyaltyPoints", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
const partner1 = models["res.partner"].get(1);
|
||||
const partner2 = models["res.partner"].get(3);
|
||||
|
||||
order.setPartner(partner1);
|
||||
|
||||
order.uiState.couponPointChanges = {
|
||||
key1: { program_id: 5, points: 100 },
|
||||
key2: { program_id: 2, points: 50 },
|
||||
};
|
||||
|
||||
order.setPartner(partner2);
|
||||
|
||||
const remainingKeys = Object.keys(order.uiState.couponPointChanges);
|
||||
expect(remainingKeys.length).toBe(1);
|
||||
expect(order.uiState.couponPointChanges[remainingKeys[0]].program_id).toBe(2);
|
||||
|
||||
// Verify getLoyaltyPoints method
|
||||
order.uiState.couponPointChanges = {
|
||||
1: {
|
||||
coupon_id: 1,
|
||||
program_id: 1,
|
||||
points: 25,
|
||||
},
|
||||
};
|
||||
|
||||
const loyaltyStats = order.getLoyaltyPoints();
|
||||
expect(loyaltyStats.length).toBe(1);
|
||||
expect(loyaltyStats[0].points.name).toBe("Points");
|
||||
expect(loyaltyStats[0].points.won).toBe(25);
|
||||
expect(loyaltyStats[0].points.balance).toBe(10);
|
||||
});
|
||||
|
||||
test("getLoyaltyPoints adapts to qty decreasing", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
const partner1 = models["res.partner"].get(1);
|
||||
order.setPartner(partner1);
|
||||
await store.orderUpdateLoyaltyPrograms();
|
||||
const reward = models["loyalty.reward"].get(3);
|
||||
const loyalty_card = models["loyalty.card"].get(4);
|
||||
const line = await addProductLineToOrder(store, order, {
|
||||
productId: 10,
|
||||
templateId: 10,
|
||||
qty: 3,
|
||||
});
|
||||
await store.orderUpdateLoyaltyPrograms();
|
||||
order._applyReward(reward, loyalty_card.id);
|
||||
const loyaltyStats = order.getLoyaltyPoints();
|
||||
expect(loyaltyStats[0].points.won).toBe(0);
|
||||
expect(loyaltyStats[0].points.spent).toBe(3);
|
||||
expect(loyaltyStats[0].points.total).toBe(0);
|
||||
expect(loyaltyStats[0].points.balance).toBe(3);
|
||||
line.setQuantity(2);
|
||||
await store.updateRewards();
|
||||
await tick();
|
||||
const loyaltyStats2 = order.getLoyaltyPoints();
|
||||
expect(loyaltyStats2[0].points.won).toBe(0);
|
||||
expect(loyaltyStats2[0].points.spent).toBe(2);
|
||||
expect(loyaltyStats2[0].points.total).toBe(1);
|
||||
expect(loyaltyStats2[0].points.balance).toBe(3);
|
||||
});
|
||||
|
||||
test("reward amount tax included cheapest product", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const order = store.addNewOrder();
|
||||
|
||||
const line = await addProductLineToOrder(store, order, {
|
||||
productId: 24,
|
||||
templateId: 24,
|
||||
qty: 1,
|
||||
});
|
||||
expect(line.prices.total_included).toBe(10);
|
||||
expect(line.prices.total_excluded).toBe(8.7);
|
||||
await store.updateRewards();
|
||||
await tick();
|
||||
expect(order.getOrderlines().length).toBe(2);
|
||||
const rewardLine = order._get_reward_lines()[0];
|
||||
expect(rewardLine.prices.total_included).toBe(-10);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { test, describe, expect } from "@odoo/hoot";
|
||||
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
|
||||
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
|
||||
import { addProductLineToOrder } from "@pos_loyalty/../tests/unit/utils";
|
||||
|
||||
definePosModels();
|
||||
|
||||
describe("pos.order.line - loyalty", () => {
|
||||
test("getEWalletGiftCardProgramType", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
// Get loyalty program #2 - type = "ewallet"
|
||||
const program = models["loyalty.program"].get(2);
|
||||
|
||||
const line = await addProductLineToOrder(store, order, {
|
||||
_e_wallet_program_id: program,
|
||||
});
|
||||
|
||||
expect(line.getEWalletGiftCardProgramType()).toBe(`${program.program_type}`);
|
||||
});
|
||||
|
||||
test("ignoreLoyaltyPoints", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
// Get loyalty program #2 - type = "ewallet"
|
||||
const programA = models["loyalty.program"].get(2);
|
||||
// Get loyalty program #4 - type = "ewallet"
|
||||
const programB = models["loyalty.program"].get(4);
|
||||
|
||||
const line = await addProductLineToOrder(store, order);
|
||||
line.update({ _e_wallet_program_id: programB });
|
||||
|
||||
expect(line.ignoreLoyaltyPoints({ program: programA })).toBe(true);
|
||||
});
|
||||
|
||||
test("getGiftCardOrEWalletBalance", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
|
||||
// Get loyalty card #3 which program_id = 3 (gift_card)
|
||||
const card = models["loyalty.card"].get(3);
|
||||
|
||||
const order = store.addNewOrder();
|
||||
|
||||
const line = await addProductLineToOrder(store, order, {
|
||||
is_reward_line: true,
|
||||
coupon_id: card,
|
||||
});
|
||||
|
||||
const balance = line.getGiftCardOrEWalletBalance();
|
||||
|
||||
expect(balance).toBeOfType("string");
|
||||
expect(balance).toMatch(new RegExp(`${card.points}`));
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { mountWithCleanup } from "@web/../tests/web_test_helpers";
|
||||
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
|
||||
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
|
||||
import { addProductLineToOrder } from "@pos_loyalty/../tests/unit/utils";
|
||||
import { TicketScreen } from "@point_of_sale/app/screens/ticket_screen/ticket_screen";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("TicketScreen.setOrder keeps reward line and triggers pos.updateRewards", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
|
||||
const order = store.addNewOrder();
|
||||
const reward = models["loyalty.reward"].get(1);
|
||||
const coupon = models["loyalty.card"].get(1);
|
||||
|
||||
await addProductLineToOrder(store, order, {
|
||||
is_reward_line: true,
|
||||
reward_id: reward,
|
||||
coupon_id: coupon,
|
||||
reward_identifier_code: "LOAD-ORDER-REWARD",
|
||||
points_cost: 10,
|
||||
});
|
||||
order.uiState.couponPointChanges = {};
|
||||
store.selectedOrderUuid = null;
|
||||
|
||||
let updateRewardsCalled = false;
|
||||
const originalUpdateRewards = store.updateRewards.bind(store);
|
||||
store.updateRewards = () => {
|
||||
updateRewardsCalled = true;
|
||||
return originalUpdateRewards();
|
||||
};
|
||||
const comp = await mountWithCleanup(TicketScreen, {});
|
||||
await comp.setOrder(order);
|
||||
|
||||
expect(updateRewardsCalled).toBe(true);
|
||||
const currentOrder = store.getOrder();
|
||||
expect(currentOrder).toBe(order);
|
||||
const rewardLines = currentOrder.lines.filter((l) => l.is_reward_line);
|
||||
expect(rewardLines.length).toBe(1);
|
||||
expect(rewardLines[0].reward_id.id).toBe(reward.id);
|
||||
expect(rewardLines[0].coupon_id.id).toBe(coupon.id);
|
||||
});
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import { test, describe, expect } from "@odoo/hoot";
|
||||
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
|
||||
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
|
||||
import { addProductLineToOrder } from "@pos_loyalty/../tests/unit/utils";
|
||||
import { onRpc } from "@web/../tests/web_test_helpers";
|
||||
|
||||
definePosModels();
|
||||
|
||||
describe("PosStore - loyalty essentials", () => {
|
||||
test("updatePrograms", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
|
||||
const partner = models["res.partner"].get(1);
|
||||
// Get loyalty program #5 with program_type = "gift_card" and is_nominative = true
|
||||
const program = models["loyalty.program"].get(5);
|
||||
|
||||
models["loyalty.program"].getAll = () => [program];
|
||||
|
||||
order.setPartner(partner);
|
||||
order._programIsApplicable = () => true;
|
||||
order._code_activated_coupon_ids = [];
|
||||
order.uiState.couponPointChanges = {};
|
||||
order.pricelist_id = { id: 1 };
|
||||
|
||||
const line = await addProductLineToOrder(store, order, {
|
||||
gift_code: "XYZ123",
|
||||
});
|
||||
|
||||
order.pointsForPrograms = () => ({
|
||||
[program.id]: [{ points: 10 }],
|
||||
});
|
||||
|
||||
await store.updatePrograms();
|
||||
|
||||
const changes = order.uiState.couponPointChanges;
|
||||
const changeList = Object.values(changes);
|
||||
|
||||
expect(changeList).toHaveLength(1);
|
||||
expect(changeList[0].program_id).toBe(program.id);
|
||||
expect(changeList[0].points).toBe(10);
|
||||
expect(changeList[0].code).toBe("XYZ123");
|
||||
expect(changeList[0].product_id).toBe(line.product_id.id);
|
||||
});
|
||||
|
||||
test("activateCode", async () => {
|
||||
onRpc("loyalty.card", "get_loyalty_card_partner_by_code", () => false);
|
||||
const store = await setupPosEnv();
|
||||
store.addNewOrder();
|
||||
const result = await store.activateCode("EXPIRED");
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("fetchLoyaltyCard", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
|
||||
// Get loyalty program #2 with program_type = "ewallet"
|
||||
const program = models["loyalty.program"].get(2);
|
||||
const partner = models["res.partner"].get(1);
|
||||
|
||||
const card = await store.fetchLoyaltyCard(program.id, partner.id);
|
||||
|
||||
expect(card.id).toBe(2);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { test, expect } from "@odoo/hoot";
|
||||
import { setupPosEnv } from "@point_of_sale/../tests/unit/utils";
|
||||
import { definePosModels } from "@point_of_sale/../tests/unit/data/generate_model_definitions";
|
||||
import { addProductLineToOrder } from "@pos_loyalty/../tests/unit/utils";
|
||||
import OrderPaymentValidation from "@point_of_sale/app/utils/order_payment_validation";
|
||||
|
||||
definePosModels();
|
||||
|
||||
test("validateOrder", async () => {
|
||||
const store = await setupPosEnv();
|
||||
const models = store.models;
|
||||
const order = store.addNewOrder();
|
||||
const fastPaymentMethod = order.config.fast_payment_method_ids[0];
|
||||
|
||||
// Get loyalty program #1 - type = "loyalty"
|
||||
const loyaltyProgram = models["loyalty.program"].get(1);
|
||||
// Get loyalty card #1 - linked to Partner #1
|
||||
const card = models["loyalty.card"].get(1);
|
||||
// Get loyalty reward #1 - type = "discount"
|
||||
const reward = models["loyalty.reward"].get(1);
|
||||
|
||||
order.uiState.couponPointChanges = {
|
||||
[card.id]: { coupon_id: card.id, program_id: loyaltyProgram.id, points: 100 },
|
||||
"-1": { coupon_id: -1, program_id: loyaltyProgram.id, points: 30, partner_id: 1 },
|
||||
};
|
||||
|
||||
await addProductLineToOrder(store, order, {
|
||||
coupon_id: card,
|
||||
is_reward_line: true,
|
||||
reward_id: reward,
|
||||
points_cost: 60,
|
||||
});
|
||||
|
||||
const validation = new OrderPaymentValidation({
|
||||
pos: store,
|
||||
orderUuid: store.getOrder().uuid,
|
||||
fastPaymentMethod: fastPaymentMethod,
|
||||
});
|
||||
|
||||
validation.isOrderValid = async () => true;
|
||||
|
||||
await validation.validateOrder();
|
||||
|
||||
expect(card.points).toBe(50);
|
||||
expect(loyaltyProgram.total_order_count).toBe(0);
|
||||
expect(order.new_coupon_info[0].code).toMatch(/^[A-Za-z0-9]+$/);
|
||||
expect(order.new_coupon_info[0].program_name).toBe(loyaltyProgram.name);
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
export const addProductLineToOrder = async (
|
||||
store,
|
||||
order,
|
||||
{ templateId = 1, productId = 1, qty = 1, price_unit = 10, ...extraFields } = {}
|
||||
) => {
|
||||
const template = store.models["product.template"].get(templateId);
|
||||
const product = store.models["product.product"].get(productId);
|
||||
|
||||
const lineData = {
|
||||
product_tmpl_id: template,
|
||||
product_id: product,
|
||||
qty,
|
||||
price_unit,
|
||||
...extraFields,
|
||||
};
|
||||
|
||||
const line = await store.addLineToOrder(lineData, order);
|
||||
|
||||
return line;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue