@devx-commerce/plugin-loyalty
Version:
A starter for Medusa plugins.
822 lines • 69.3 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@medusajs/framework/utils");
const helper_1 = require("../../utils/helper");
const models_1 = require("./models");
class LoyaltyModuleService extends (0, utils_1.MedusaService)({
Event: models_1.EventModel,
Wallet: models_1.WalletModel,
// Transaction: LoyaltyTransaction,
LoyaltyTransaction: models_1.LoyaltyTransaction,
Referral: models_1.ReferralModel,
TransactionBucket: models_1.TransactionBucketModel,
TransactionHistory: models_1.TransactionHistoryModel,
Config: models_1.ConfigModel,
}) {
constructor(container) {
super(container);
this.logger_ = container.logger;
this.logger_.info("[LoyaltyModuleService]: Service initialized");
}
async addPoints_({ customer_id, points, referenceType = "manual", description, expires_at, order_id, order_display_id, referrer, referee, metadata, notes, }, sharedContext) {
if (points <= 0)
return null;
this.logger_.info(`[LoyaltyModuleService]: Adding ${points} points for customer ${customer_id}`);
const wallet = await this.createOrRetrieveWallet(customer_id, sharedContext);
// Calculate running balance: current points + new points (credit)
const running_balance = wallet.points + points;
const transaction = await this.createLoyaltyTransactions({
customer_id: customer_id,
points,
type: "credit",
loyalty_wallet_id: wallet.id,
reference_type: referenceType,
description: description || "Points added",
expires_at,
order_id,
order_display_id,
referrer,
referee,
running_balance,
metadata,
notes,
}, sharedContext);
await this.createTransactionBuckets({
loyalty_wallet_id: wallet.id,
loyalty_transaction_id: transaction.id,
customer_id: customer_id,
original_points: points,
remaining_points: points,
status: "active",
reference_type: referenceType,
description: description || "Points added",
expires_at,
}, sharedContext);
// Derive the balance from the ledger (which now includes the bucket created
// above) instead of `wallet.points + points`. The arithmetic form carries
// any pre-existing drift forward and compounds it: a wallet left at 72 by a
// failed expiry reconciliation became 72 + 83 = 155, and the customer could
// then redeem 155 when only 83 were fundable. Recomputing makes every credit
// a reconciliation point, so drift cannot survive a credit.
//
// `total_earned_points` is a lifetime counter, not a balance, so it stays
// additive — it is not derived from the currently-active buckets.
const activeBuckets = await this.listTransactionBuckets({
status: "active",
loyalty_wallet_id: wallet.id,
}, undefined, sharedContext);
const totals = this.sumBuckets(activeBuckets);
const updatedWallet = await this.updateWallets({
id: wallet.id,
points: totals.points,
perpetual_points: totals.perpetual_points,
total_earned_points: wallet.total_earned_points + points,
}, sharedContext);
this.logger_.info(`[LoyaltyModuleService]: Successfully added ${points} points for customer ${customer_id}: ${description ? description : ""} `);
return updatedWallet;
}
async addPoints(params, sharedContext) {
return await this.addPoints_(params, sharedContext);
}
async deductPoints_(params, sharedContext) {
const { customerId, points, referenceType, description, expires_at, order_id, order_display_id, metadata, notes, } = params;
const wallet = await this.createOrRetrieveWallet(customerId, sharedContext);
// Read the authoritative ledger up front. `wallet.points` is a cached
// rollup and can sit above the buckets, so validating against it would let
// an under-funded debit through — draining every bucket, charging the
// wallet in full, and widening the gap. Everything below is derived from
// the buckets instead.
const activeBuckets = await this.listTransactionBuckets({
status: "active",
loyalty_wallet_id: wallet.id,
}, {
// Spend soonest-expiring first so points are not wasted.
order: {
expires_at: "ASC",
},
}, sharedContext);
const availableBucketPoints = this.sumBuckets(activeBuckets).points;
if (availableBucketPoints < points) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Insufficient points");
}
// Running balance is reported to the customer, so base it on the ledger too.
const running_balance = availableBucketPoints - points;
const transaction = await this.createLoyaltyTransactions({
customer_id: customerId,
points,
type: "debit",
loyalty_wallet_id: wallet.id,
reference_type: referenceType,
description: description || "Points deducted",
expires_at,
order_id,
order_display_id,
running_balance,
metadata,
notes,
}, sharedContext);
await this.updateRemainingPointsInBucketsAndHistory(activeBuckets, points, transaction.id, order_id, order_display_id, sharedContext);
// Derive the new balances from the buckets we just wrote rather than from
// `wallet.points - points`. The subtraction assumed the buckets always
// funded the debit in full; when they did not, the wallet drifted above the
// ledger and stayed there. Recomputing keeps the rollup exact by
// construction, and self-heals pre-existing drift on the next debit.
//
// `updateRemainingPointsInBucketsAndHistory` mutates the bucket objects in
// `activeBuckets` in place, so re-reducing them here reflects the deduction
// that was just applied.
const totals = this.sumBuckets(activeBuckets);
const updatedWallet = await this.updateWallets({
id: wallet.id,
points: totals.points,
perpetual_points: totals.perpetual_points,
total_used_points: referenceType === "order" ||
referenceType === "order_return" ||
referenceType === "manual"
? wallet.total_used_points + points
: wallet.total_used_points,
total_expired_points: referenceType === "expired"
? wallet.total_expired_points + points
: wallet.total_expired_points,
}, sharedContext);
this.logger_.info(`[LoyaltyModuleService]: Successfully deducted ${points} points for customer ${customerId}: ${description} `);
return updatedWallet;
}
async deductPoints(params, sharedContext) {
return await this.deductPoints_(params, sharedContext);
}
async updateRemainingPointsInBucketsAndHistory(buckets, pointsToDeduct, transaction_id, order_id, order_display_id, sharedContext) {
try {
const updatedBuckets = [];
const history = [];
let deductedPerpetualPoints = 0;
for (const bucket of buckets) {
if (pointsToDeduct <= 0)
break;
const availablePoints = bucket.remaining_points;
if (availablePoints <= 0) {
continue; // Skip depleted buckets
}
const deduction = Math.min(pointsToDeduct, availablePoints);
// Track perpetual points being deducted
const isPerpetualBucket = !bucket.expires_at;
if (isPerpetualBucket) {
deductedPerpetualPoints += deduction;
}
bucket.remaining_points = availablePoints - deduction;
pointsToDeduct -= deduction;
if (bucket.remaining_points === 0) {
bucket.status = "depleted";
}
updatedBuckets.push(bucket);
const historyItem = {
loyalty_transaction_bucket_id: bucket.id,
loyalty_wallet_id: bucket.loyalty_wallet_id,
points_used: deduction,
type: "debit",
status: "active",
reference_type: bucket.reference_type,
customer_id: bucket.customer_id,
order_id: order_id || null,
order_display_id: order_display_id || null,
loyalty_transaction_id: transaction_id,
};
history.push(historyItem);
}
await this.createTransactionHistories(history, sharedContext);
await this.updateTransactionBuckets(updatedBuckets, sharedContext);
return { deductedPerpetualPoints };
}
catch (error) {
throw error;
}
}
/**
* Returns the customer's spendable balance.
*
* The active transaction buckets are the authoritative ledger — they are what
* `deductPoints_` actually spends from. `wallet.points` is only a cached
* rollup of them, maintained by arithmetic (`points ± n`) in several places,
* so any failure between a bucket write and the matching wallet write leaves
* the cache permanently above the ledger. Reading the cache then lets a
* customer redeem points that no bucket can fund: checkout discounts the
* order, and the debit later fails or under-deducts.
*
* So derive the balance from the buckets, and repair the cache whenever it
* has drifted. Every read is a reconciliation point, which means existing
* drift heals itself the next time the customer is looked at, rather than
* needing a migration.
*/
async getPoints(customerId) {
const [wallet] = await this.listWallets({ customer_id: customerId });
if (!wallet) {
return 0;
}
const buckets = await this.listTransactionBuckets({
status: "active",
loyalty_wallet_id: wallet.id,
});
const { points } = this.sumBuckets(buckets);
if (wallet.points !== points) {
this.logger_.warn(`[LoyaltyModuleService]: wallet ${wallet.id} (customer ${customerId}) ` +
`had ${wallet.points} points but active buckets hold ${points}. ` +
`Correcting the wallet to match the ledger.`);
await this.calculateWalletPoints(wallet.id);
}
return points;
}
async getTransactionHistory(customerId, options = {}) {
const { take = 10, skip = 0, type, referenceType } = options;
const query = {
customer_id: customerId,
};
if (type) {
query.type = type;
}
if (referenceType) {
query.reference_type = referenceType;
}
return await this.listLoyaltyTransactions(query, {
take,
skip,
order: { created_at: "DESC" },
});
}
async calculatePointsFromAmount(amount) {
const config = await this.listConfigs();
const { points_per_rupee } = config[0];
const points = Math.floor(amount * (points_per_rupee || 1));
if (points < 0) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Amount cannot be negative");
}
return points;
}
async calculatePointsToAddFromOrderEvent(event, orderTotal) {
const { earn_per_order, earn_per_order_type, max_earn_per_order, min_order_value, expiry_days, } = event;
if (min_order_value && orderTotal < min_order_value) {
return { pointsToAdd: 0, expires_at: undefined };
}
if (!earn_per_order) {
return { pointsToAdd: 0, expires_at: undefined };
}
// Calculate expiry date if applicable
const expires_at = expiry_days
? new Date(Date.now() + expiry_days * 24 * 60 * 60 * 1000)
: undefined;
let rawPoints = earn_per_order_type === "percentage"
? orderTotal * (earn_per_order / 100)
: earn_per_order;
if (max_earn_per_order) {
rawPoints = Math.min(rawPoints, max_earn_per_order);
}
const pointsToAdd = Math.ceil(rawPoints);
return { pointsToAdd, expires_at };
}
async calculatePointsOnCart(cartTotal, availablePoints, orderValue) {
const [config] = await this.listConfigs();
if (!config) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Loyalty configuration not found");
}
const { max_spend_per_order, min_order_value, spend_per_order, spend_per_order_type, points_per_rupee, } = config;
if (orderValue < min_order_value) {
return 0;
}
let applicableAmount = spend_per_order
? spend_per_order_type === "percentage"
? cartTotal * (spend_per_order / 100)
: spend_per_order
: 0;
if (max_spend_per_order) {
applicableAmount = Math.min(applicableAmount, max_spend_per_order);
}
const points = await this.calculatePointsFromAmount(applicableAmount);
if (availablePoints < points) {
const amount = (0, helper_1.calculateAmountFromPoints)(availablePoints, points_per_rupee);
if (amount <= 0) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Insufficient points");
}
return amount;
}
return Math.floor(applicableAmount);
}
async createReferral(customerId) {
let customer = await this.createOrRetrieveWallet(customerId);
const referral = await this.createReferrals({
customer_id: customer.id,
referral_code: `${Date.now()}`,
});
return referral;
}
async createOrRetrieveWallet(customerId, sharedContext) {
const wallet = await this.listWallets({
customer_id: customerId,
}, undefined, sharedContext);
if (wallet.length === 0) {
return await this.createWallets({
customer_id: customerId,
points: 0,
}, undefined, sharedContext);
}
return wallet[0];
}
async calculatePointForReferralOrder(event, referral, amount) {
const { referee_bonus_per_order, referrer_bonus_per_order, referee_bonus_per_order_type, referrer_bonus_per_order_type, expiry_days, } = event;
let referee_bonus = referee_bonus_per_order_type === "percentage" && referee_bonus_per_order
? amount * (referee_bonus_per_order / 100)
: referee_bonus_per_order;
let referrer_bonus = referrer_bonus_per_order_type === "percentage" && referrer_bonus_per_order
? amount * (referrer_bonus_per_order / 100)
: referrer_bonus_per_order;
return {
referee: {
bonus: referee_bonus ? Math.round(referee_bonus) : 0,
expires_at: expiry_days
? new Date(Date.now() + expiry_days * 24 * 60 * 60 * 1000)
: null,
},
referrer: {
bonus: referrer_bonus ? Math.round(referrer_bonus) : 0,
customer_id: referral.customer_id,
expires_at: expiry_days
? new Date(Date.now() + expiry_days * 24 * 60 * 60 * 1000)
: null,
},
};
}
async addReferralCodeInWallet(customerId, referralCode) {
const wallet = await this.createOrRetrieveWallet(customerId);
const usedReferralCode = wallet?.used_referrals || [];
const codes = [...new Set([...usedReferralCode, referralCode])].filter(Boolean);
await this.updateWallets({
id: wallet.id,
used_referrals: codes,
});
}
async expireTransaction_(bucket, sharedContext) {
try {
// Re-read the bucket instead of trusting the caller's copy. The passed-in
// object is a snapshot: it may have been depleted by a debit since the
// batch was selected, or the same bucket may appear twice in one batch.
// Acting on the snapshot wrote a second "Points expired" transaction for
// points that were already gone — the wallet still ended up correct
// (`calculateWalletPoints` re-derives it), but the ledger double-counted
// the expiry, which corrupts history and any reporting built on it.
const current = await this.retrieveTransactionBucket(bucket.id, undefined, sharedContext);
if (!current || current.status !== "active") {
return;
}
const { loyalty_transaction_id, remaining_points } = current;
if (remaining_points <= 0) {
// Nothing left to expire, but the bucket should not stay `active` or the
// nightly job will keep re-selecting it.
await this.updateTransactionBuckets({ id: current.id, status: "expired" }, sharedContext);
return;
}
const transaction = await this.retrieveLoyaltyTransaction(loyalty_transaction_id, undefined, sharedContext);
// Get current wallet to calculate running balance
const wallet = await this.retrieveWallet(transaction.loyalty_wallet_id, undefined, sharedContext);
const reverseTransaction = {
type: "debit",
description: `Points expired`,
reference_type: "expired",
points: remaining_points,
loyalty_wallet_id: transaction.loyalty_wallet_id,
customer_id: transaction.customer_id,
running_balance: wallet.points - remaining_points,
order_display_id: transaction.order_display_id,
metadata: null,
};
await this.createLoyaltyTransactions(reverseTransaction, sharedContext);
// Update by id rather than spreading the whole row — spreading writes
// back every field from a snapshot that may be stale.
await this.updateTransactionBuckets({
id: current.id,
status: "expired",
remaining_points: 0,
}, sharedContext);
await this.createTransactionHistories({
loyalty_transaction_bucket_id: current.id,
loyalty_wallet_id: current.loyalty_wallet_id,
points_used: remaining_points,
type: "debit",
status: "active",
reference_type: "expired",
customer_id: transaction.customer_id,
order_id: transaction.order_id,
order_display_id: transaction.order_display_id,
}, sharedContext);
}
catch (error) {
this.logger_.error("Error in expire transaction", error);
throw error;
}
}
/**
* Runs `task` over `items` a chunk at a time and returns the successful
* results, so one rejection cannot abort the rest of the batch.
*
* Bounded concurrency matters here: firing a whole expiry batch at once
* exhausts the Knex connection pool ("Timeout acquiring a connection"), and
* every timed-out call leaves a wallet inflated.
*/
async runInChunks(items, task, onError, chunkSize = 25) {
const results = [];
for (let index = 0; index < items.length; index += chunkSize) {
const settled = await Promise.allSettled(items.slice(index, index + chunkSize).map(task));
for (const outcome of settled) {
if (outcome.status === "fulfilled") {
results.push(outcome.value);
}
else {
onError(outcome.reason);
}
}
}
return results;
}
/**
* Expires the given buckets and brings each affected wallet back in line.
*
* `expireTransaction_` empties a bucket but deliberately leaves the wallet
* alone; `calculateWalletPoints` is what re-derives the balance. Those two
* steps must not come apart, because a wallet left holding points whose
* buckets are empty stays wrong forever — the expiry query only selects
* buckets with `remaining_points > 0`, so an already-emptied bucket is never
* revisited, and every later credit compounds the phantom balance.
*
* Failures are therefore isolated per bucket and per wallet rather than
* failing the batch, and reconciliation runs only for buckets that actually
* expired.
*/
async expireTransactionsByBucket(buckets) {
if (!buckets.length) {
return;
}
const expiryFailures = [];
const reconcileFailures = [];
// De-duplicate by id before processing. `expireTransaction_` re-reads the
// bucket and skips it unless it is still `active`, but that guard cannot
// help when the same bucket appears twice in one chunk: both copies run
// concurrently and both read `active` before either writes. The result was
// one "Points expired" transaction per copy — the wallet stayed correct
// (it is re-derived from buckets) while the ledger double-counted.
const uniqueBuckets = [
...new Map(buckets.map((bucket) => [bucket.id, bucket])).values(),
];
const expired = await this.runInChunks(uniqueBuckets, async (bucket) => {
await this.expireTransaction_(bucket);
return bucket.loyalty_wallet_id;
}, (reason) => expiryFailures.push(reason));
const walletIds = [...new Set(expired)];
await this.runInChunks(walletIds, (walletId) => this.calculateWalletPoints(walletId), (reason) => reconcileFailures.push(reason));
// A wallet whose reconciliation failed is now inflated and will not be
// picked up again, so surface both failure kinds separately.
for (const [label, failures] of [
["bucket expiries", expiryFailures],
["wallet reconciliations", reconcileFailures],
]) {
if (failures.length) {
this.logger_.error(`[LoyaltyModuleService]: ${failures.length} ${label} failed. ` +
`First error: ${failures[0]?.message ?? failures[0]}`);
}
}
}
async reverseDebitTransaction(transaction, sharedContext) {
const histories = await this.listTransactionHistories({
loyalty_transaction_id: transaction.id,
}, undefined, sharedContext);
if (histories.length === 0)
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "No history found");
// Fetch every affected bucket in one query instead of one per history row,
// and write both tables in bulk. The previous per-row fan-out issued 2N
// concurrent queries, which is the pattern that exhausts the Knex pool.
const bucketIds = [
...new Set(histories.map((h) => h.loyalty_transaction_bucket_id)),
];
const buckets = await this.listTransactionBuckets({ id: bucketIds }, undefined, sharedContext);
const bucketsById = new Map(buckets.map((bucket) => [bucket.id, bucket]));
const restored = histories.map((h) => {
const bucket = bucketsById.get(h.loyalty_transaction_bucket_id);
if (!bucket) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "No bucket found");
}
// Several histories can point at the same bucket, so accumulate onto the
// running value rather than the originally-read one.
bucket.remaining_points += h.points_used;
return bucket;
});
await this.updateTransactionHistories(histories.map((h) => ({ id: h.id, status: "reversed" })), sharedContext);
await this.updateTransactionBuckets([...new Set(restored)].map((bucket) => ({
id: bucket.id,
remaining_points: bucket.remaining_points,
status: "active",
})), sharedContext);
}
async reverseCreditTransaction(transaction, sharedContext) {
const [bucket] = await this.listTransactionBuckets({
loyalty_transaction_id: transaction.id,
status: "active",
}, undefined, sharedContext);
// There may be no active bucket at all: once the customer has spent these
// points the bucket is `depleted`, and once it lapses it is `expired`.
// Either way there is nothing left to claw back — reversing a credit only
// removes points the customer still holds. Dereferencing blindly here threw
// and aborted the whole reversal, leaving the remaining transactions of a
// returned order unprocessed.
if (!bucket) {
return;
}
const remainingPoints = bucket.remaining_points;
if (remainingPoints === 0)
return;
//create history
await this.createTransactionHistories({
loyalty_transaction_bucket_id: bucket.id,
points_used: remainingPoints,
type: "debit",
status: "active",
reference_type: "order_return",
customer_id: transaction.customer_id,
order_id: transaction.order_id,
order_display_id: transaction.order_display_id,
loyalty_wallet_id: transaction.loyalty_wallet_id,
}, sharedContext);
//update bucket status and remaining points
await this.updateTransactionBuckets({
id: bucket.id,
status: "depleted",
remaining_points: 0,
}, sharedContext);
}
async reverseTransaction(transaction, sharedContext) {
// Get current wallet to calculate running balance
const wallet = await this.retrieveWallet(transaction.loyalty_wallet_id, undefined, sharedContext);
// Calculate running balance for reversal transaction
const isReverseCredit = transaction.type === "debit";
const running_balance = isReverseCredit
? wallet.points + transaction.points // Reversing debit = credit
: wallet.points - transaction.points; // Reversing credit = debit
//create reverse transaction
const reversed = {
type: transaction.type === "debit" ? "credit" : "debit",
description: `Points reversed`,
reference_type: "order_return",
points: transaction.points,
loyalty_wallet_id: transaction.loyalty_wallet_id,
customer_id: transaction.customer_id,
order_id: transaction.order_id,
order_display_id: transaction.order_display_id,
running_balance,
metadata: null,
};
await this.createLoyaltyTransactions(reversed, sharedContext);
//update transaction status
await this.updateLoyaltyTransactions({ id: transaction.id, status: "reversed" }, sharedContext);
}
async reverseOrderTransactions(transactions, sharedContext) {
const affectedWalletIds = new Set();
for (const transaction of transactions) {
if (transaction.type === "debit") {
await this.reverseDebitTransaction(transaction, sharedContext);
}
else {
await this.reverseCreditTransaction(transaction, sharedContext);
}
await this.reverseTransaction(transaction, sharedContext);
affectedWalletIds.add(transaction.loyalty_wallet_id);
}
// Both reversal paths move points in or out of buckets but leave the wallet
// untouched, so without this the cached balance keeps the pre-reversal
// value — the same divergence that let customers redeem points no bucket
// could fund. The wallet is a rollup of the buckets, so re-derive it
// whenever the buckets move.
for (const walletId of affectedWalletIds) {
await this.calculateWalletPoints(walletId, sharedContext);
}
}
/**
* Rewrites a wallet's cached balance from its active buckets.
*
* THE core invariant of this module: the active transaction buckets are the
* ledger, and `wallet.points` is only a cached rollup of them. Any code that
* moves points in or out of buckets must call this afterwards; anything that
* maintains the cache by arithmetic (`points ± n`) instead will carry drift
* forward forever, because nothing re-reads the ledger to notice.
*/
async calculateWalletPoints(walletId, sharedContext) {
const buckets = await this.listTransactionBuckets({
loyalty_wallet_id: walletId,
status: "active",
}, undefined, sharedContext);
const totals = this.sumBuckets(buckets);
await this.updateWallets({
id: walletId,
points: totals.points,
perpetual_points: totals.perpetual_points,
}, sharedContext);
return totals;
}
/**
* Totals a set of buckets into the two figures a wallet caches. Kept separate
* so every caller derives them identically — the perpetual/expirable split is
* easy to get subtly wrong when it is re-implemented inline.
*/
sumBuckets(buckets) {
let points = 0;
let perpetual_points = 0;
for (const bucket of buckets) {
points += bucket.remaining_points;
if (!bucket.expires_at) {
perpetual_points += bucket.remaining_points;
}
}
return { points, perpetual_points };
}
async expireReferrals(referrals) {
if (!referrals.length) {
return;
}
// One bulk update rather than N concurrent ones — the unbounded fan-out
// form is what exhausts the Knex pool on large nightly batches.
await this.updateReferrals(referrals.map((referral) => ({
id: referral.id,
status: "expired",
})));
}
async applyReferralBonus({ referee, referrer, referee_bonus, referrer_bonus, referral_code, expires_at, order_id, order_display_id, metadata, }) {
const referrerWallet = await this.createOrRetrieveWallet(referrer);
const refereeWallet = await this.createOrRetrieveWallet(referee);
if (referral_code) {
if (referee_bonus && referee_bonus > 0) {
await this.addPoints({
customer_id: referee,
points: referee_bonus,
description: order_id
? `Referral bonus for order ${order_id}`
: "Referral bonus",
expires_at,
order_id,
order_display_id,
referrer: referrerWallet.id,
referenceType: order_id ? "referral_order" : "referral",
metadata,
});
await this.addReferralCodeInWallet(referee, referral_code);
}
if (referrer_bonus && referrer_bonus > 0) {
await this.addPoints({
customer_id: referrer,
points: referrer_bonus,
description: order_id
? `Referrer bonus for order ${order_id}`
: "Referrer bonus",
expires_at,
order_id,
order_display_id,
referee: refereeWallet.id,
referenceType: order_id ? "referral_order" : "referral",
metadata,
});
}
}
return {
referee: {
bonus: referee_bonus,
expires_at,
},
referrer: {
bonus: referrer_bonus,
expires_at,
},
};
}
async redeemReferralLink(customerId, referralCode) {
const [referral] = await this.listReferrals({
referral_code: referralCode,
});
if (!referral) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Referral not found");
}
const referrer = referral.customer_id;
if (referrer === customerId) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Referral cannot be redeemed by the referrer");
}
const [event] = await this.listEvents({
trigger_event: "referral_link_redeemed",
status: "active",
});
if (!event) {
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "referral_link_redeemed event not found");
}
const { referrer_bonus, referee_bonus } = event;
const res = await this.applyReferralBonus({
referee: customerId,
referrer: referrer,
referee_bonus: referee_bonus,
referrer_bonus: referrer_bonus,
referral_code: referralCode,
expires_at: event.expiry_days
? new Date(Date.now() + event.expiry_days * 24 * 60 * 60 * 1000)
: undefined,
});
return res;
}
}
LoyaltyModuleService.linkable = {
loyaltyWallet: models_1.WalletModel,
};
__decorate([
(0, utils_1.InjectTransactionManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "addPoints_", null);
__decorate([
(0, utils_1.InjectManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "addPoints", null);
__decorate([
(0, utils_1.InjectTransactionManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "deductPoints_", null);
__decorate([
(0, utils_1.InjectManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "deductPoints", null);
__decorate([
__param(5, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Array, Number, String, String, Number, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "updateRemainingPointsInBucketsAndHistory", null);
__decorate([
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "createOrRetrieveWallet", null);
__decorate([
(0, utils_1.InjectTransactionManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "expireTransaction_", null);
__decorate([
(0, utils_1.InjectTransactionManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "reverseDebitTransaction", null);
__decorate([
(0, utils_1.InjectTransactionManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "reverseCreditTransaction", null);
__decorate([
(0, utils_1.InjectTransactionManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "reverseTransaction", null);
__decorate([
(0, utils_1.InjectTransactionManager)(),
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Array, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "reverseOrderTransactions", null);
__decorate([
__param(1, (0, utils_1.MedusaContext)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", Promise)
], LoyaltyModuleService.prototype, "calculateWalletPoints", null);
exports.default = LoyaltyModuleService;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL2xveWFsdHkvc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7OztBQUNBLHFEQU1tQztBQUluQywrQ0FBK0Q7QUFDL0QscUNBUWtCO0FBcUJsQixNQUFNLG9CQUFxQixTQUFRLElBQUEscUJBQWEsRUFBQztJQUMvQyxLQUFLLEVBQUUsbUJBQVU7SUFDakIsTUFBTSxFQUFFLG9CQUFXO0lBQ25CLHFDQUFxQztJQUNyQyxrQkFBa0IsRUFBbEIsMkJBQWtCO0lBQ2xCLFFBQVEsRUFBRSxzQkFBYTtJQUN2QixpQkFBaUIsRUFBRSwrQkFBc0I7SUFDekMsa0JBQWtCLEVBQUUsZ0NBQXVCO0lBQzNDLE1BQU0sRUFBRSxvQkFBVztDQUNwQixDQUFDO0lBR0EsWUFBWSxTQUFTO1FBQ25CLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUNqQixJQUFJLENBQUMsT0FBTyxHQUFHLFNBQVMsQ0FBQyxNQUFNLENBQUM7UUFDaEMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsNkNBQTZDLENBQUMsQ0FBQztJQUNuRSxDQUFDO0lBT2UsQUFBTixLQUFLLENBQUMsVUFBVSxDQUN4QixFQUNFLFdBQVcsRUFDWCxNQUFNLEVBQ04sYUFBYSxHQUFHLFFBQVEsRUFDeEIsV0FBVyxFQUNYLFVBQVUsRUFDVixRQUFRLEVBQ1IsZ0JBQWdCLEVBQ2hCLFFBQVEsRUFDUixPQUFPLEVBQ1AsUUFBUSxFQUNSLEtBQUssR0FtQk4sRUFDZ0IsYUFBc0M7UUFFdkQsSUFBSSxNQUFNLElBQUksQ0FBQztZQUFFLE9BQU8sSUFBSSxDQUFDO1FBRTdCLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUNmLGtDQUFrQyxNQUFNLHdCQUF3QixXQUFXLEVBQUUsQ0FDOUUsQ0FBQztRQUVGLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLHNCQUFzQixDQUM5QyxXQUFXLEVBQ1gsYUFBYSxDQUNkLENBQUM7UUFFRixrRUFBa0U7UUFDbEUsTUFBTSxlQUFlLEdBQUcsTUFBTSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFFL0MsTUFBTSxXQUFXLEdBQUcsTUFBTSxJQUFJLENBQUMseUJBQXlCLENBQ3REO1lBQ0UsV0FBVyxFQUFFLFdBQVc7WUFDeEIsTUFBTTtZQUNOLElBQUksRUFBRSxRQUFRO1lBQ2QsaUJBQWlCLEVBQUUsTUFBTSxDQUFDLEVBQUU7WUFDNUIsY0FBYyxFQUFFLGFBQWE7WUFDN0IsV0FBVyxFQUFFLFdBQVcsSUFBSSxjQUFjO1lBQzFDLFVBQVU7WUFDVixRQUFRO1lBQ1IsZ0JBQWdCO1lBQ2hCLFFBQVE7WUFDUixPQUFPO1lBQ1AsZUFBZTtZQUNmLFFBQVE7WUFDUixLQUFLO1NBQ04sRUFDRCxhQUFhLENBQ2QsQ0FBQztRQUVGLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUNqQztZQUNFLGlCQUFpQixFQUFFLE1BQU0sQ0FBQyxFQUFFO1lBQzVCLHNCQUFzQixFQUFFLFdBQVcsQ0FBQyxFQUFFO1lBQ3RDLFdBQVcsRUFBRSxXQUFXO1lBQ3hCLGVBQWUsRUFBRSxNQUFNO1lBQ3ZCLGdCQUFnQixFQUFFLE1BQU07WUFDeEIsTUFBTSxFQUFFLFFBQVE7WUFDaEIsY0FBYyxFQUFFLGFBQWE7WUFDN0IsV0FBVyxFQUFFLFdBQVcsSUFBSSxjQUFjO1lBQzFDLFVBQVU7U0FDSixFQUNSLGFBQWEsQ0FDZCxDQUFDO1FBRUYsNEVBQTRFO1FBQzVFLDBFQUEwRTtRQUMxRSw0RUFBNEU7UUFDNUUsNEVBQTRFO1FBQzVFLDZFQUE2RTtRQUM3RSw0REFBNEQ7UUFDNUQsRUFBRTtRQUNGLDBFQUEwRTtRQUMxRSxrRUFBa0U7UUFDbEUsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQ3JEO1lBQ0UsTUFBTSxFQUFFLFFBQVE7WUFDaEIsaUJBQWlCLEVBQUUsTUFBTSxDQUFDLEVBQUU7U0FDN0IsRUFDRCxTQUFTLEVBQ1QsYUFBYSxDQUNkLENBQUM7UUFFRixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBRTlDLE1BQU0sYUFBYSxHQUFHLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FDNUM7WUFDRSxFQUFFLEVBQUUsTUFBTSxDQUFDLEVBQUU7WUFDYixNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU07WUFDckIsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDLGdCQUFnQjtZQUN6QyxtQkFBbUIsRUFBRSxNQUFNLENBQUMsbUJBQW1CLEdBQUcsTUFBTTtTQUN6RCxFQUNELGFBQWEsQ0FDZCxDQUFDO1FBRUYsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQ2YsOENBQThDLE1BQU0sd0JBQXdCLFdBQVcsS0FDckYsV0FBVyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQzlCLEdBQUcsQ0FDSixDQUFDO1FBQ0YsT0FBTyxhQUFvQixDQUFDO0lBQzlCLENBQUM7SUFHSyxBQUFOLEtBQUssQ0FBQyxTQUFTLENBQ2IsTUFrQkMsRUFDZ0IsYUFBc0M7UUFFdkQsT0FBTyxNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxFQUFFLGFBQWEsQ0FBQyxDQUFDO0lBQ3RELENBQUM7SUFFZSxBQUFOLEtBQUssQ0FBQyxhQUFhLENBQzNCLE1BZUMsRUFDZ0IsYUFBc0M7UUFFdkQsTUFBTSxFQUNKLFVBQVUsRUFDVixNQUFNLEVBQ04sYUFBYSxFQUNiLFdBQVcsRUFDWCxVQUFVLEVBQ1YsUUFBUSxFQUNSLGdCQUFnQixFQUNoQixRQUFRLEVBQ1IsS0FBSyxHQUNOLEdBQUcsTUFBTSxDQUFDO1FBRVgsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUMsVUFBVSxFQUFFLGFBQWEsQ0FBQyxDQUFDO1FBRTVFLHNFQUFzRTtRQUN0RSwyRUFBMkU7UUFDM0Usc0VBQXNFO1FBQ3RFLHlFQUF5RTtRQUN6RSx1QkFBdUI7UUFDdkIsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQ3JEO1lBQ0UsTUFBTSxFQUFFLFFBQVE7WUFDaEIsaUJBQWlCLEVBQUUsTUFBTSxDQUFDLEVBQUU7U0FDN0IsRUFDRDtZQUNFLHlEQUF5RDtZQUN6RCxLQUFLLEVBQUU7Z0JBQ0wsVUFBVSxFQUFFLEtBQUs7YUFDbEI7U0FDRixFQUNELGFBQWEsQ0FDZCxDQUFDO1FBRUYsTUFBTSxxQkFBcUIsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLGFBQWEsQ0FBQyxDQUFDLE1BQU0sQ0FBQztRQUVwRSxJQUFJLHFCQUFxQixHQUFHLE1BQU0sRUFBRSxDQUFDO1lBQ25DLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLHFCQUFxQixDQUN0QixDQUFDO1FBQ0osQ0FBQztRQUVELDZFQUE2RTtRQUM3RSxNQUFNLGVBQWUsR0FBRyxxQkFBcUIsR0FBRyxNQUFNLENBQUM7UUFFdkQsTUFBTSxXQUFXLEdBQUcsTUFBTSxJQUFJLENBQUMseUJBQXlCLENBQ3REO1lBQ0UsV0FBVyxFQUFFLFVBQVU7WUFDdkIsTUFBTTtZQUNOLElBQUksRUFBRSxPQUFPO1lBQ2IsaUJBQWlCLEVBQUUsTUFBTSxDQUFDLEVBQUU7WUFDNUIsY0FBYyxFQUFFLGFBQWE7WUFDN0IsV0FBVyxFQUFFLFdBQVcsSUFBSSxpQkFBaUI7WUFDN0MsVUFBVTtZQUNWLFFBQVE7WUFDUixnQkFBZ0I7WUFDaEIsZUFBZTtZQUNmLFFBQVE7WUFDUixLQUFLO1NBQ04sRUFDRCxhQUFhLENBQ2QsQ0FBQztRQUVGLE1BQU0sSUFBSSxDQUFDLHdDQUF3QyxDQUNqRCxhQUFhLEVBQ2IsTUFBTSxFQUNOLFdBQVcsQ0FBQyxFQUFFLEVBQ2QsUUFBUSxFQUNSLGdCQUFnQixFQUNoQixhQUFhLENBQ2QsQ0FBQztRQUVGLDBFQUEwRTtRQUMxRSx1RUFBdUU7UUFDdkUsNEVBQTRFO1FBQzVFLGlFQUFpRTtRQUNqRSxxRUFBcUU7UUFDckUsRUFBRTtRQUNGLDJFQUEyRTtRQUMzRSw0RUFBNEU7UUFDNUUseUJBQXlCO1FBQ3pCLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxDQUFDLENBQUM7UUFFOUMsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsYUFBYSxDQUM1QztZQUNFLEVBQUUsRUFBRSxNQUFNLENBQUMsRUFBRTtZQUNiLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTTtZQUNyQixnQkFBZ0IsRUFBRSxNQUFNLENBQUMsZ0JBQWdCO1lBQ3pDLGlCQUFpQixFQUNmLGFBQWEsS0FBSyxPQUFPO2dCQUN6QixhQUFhLEtBQUssY0FBYztnQkFDaEMsYUFBYSxLQUFLLFFBQVE7Z0JBQ3hCLENBQUMsQ0FBQyxNQUFNLENBQUMsaUJBQWlCLEdBQUcsTUFBTTtnQkFDbkMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxpQkFBaUI7WUFDOUIsb0JBQW9CLEVBQ2xCLGFBQWEsS0FBSyxTQUFTO2dCQUN6QixDQUFDLENBQUMsTUFBTSxDQUFDLG9CQUFvQixHQUFHLE1BQU07Z0JBQ3RDLENBQUMsQ0FBQyxNQUFNLENBQUMsb0JBQW9CO1NBQ2xDLEVBQ0QsYUFBYSxDQUNkLENBQUM7UUFFRixJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FDZixpREFBaUQsTUFBTSx3QkFBd0IsVUFBVSxLQUFLLFdBQVcsR0FBRyxDQUM3RyxDQUFDO1FBQ0YsT0FBTyxhQUFvQixDQUFDO0lBQzlCLENBQUM7SUFHSyxBQUFOLEtBQUssQ0FBQyxZQUFZLENBQ2hCLE1BZUMsRUFDZ0IsYUFBc0M7UUFFdkQsT0FBTyxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUMsTUFBTSxFQUFFLGFBQWEsQ0FBQyxDQUFDO0lBQ3pELENBQUM7SUFFSyxBQUFOLEtBQUssQ0FBQyx3Q0FBd0MsQ0FDNUMsT0FBcUQsRUFDckQsY0FBc0IsRUFDdEIsY0FBc0IsRUFDdEIsUUFBaUIsRUFDakIsZ0JBQXlCLEVBQ1IsYUFBc0M7UUFFdkQsSUFBSSxDQUFDO1lBQ0gsTUFBTSxjQUFjLEdBQWlELEVBQUUsQ0FBQztZQUN4RSxNQUFNLE9BQU8sR0FBNkIsRUFBRSxDQUFDO1lBRTdDLElBQUksdUJBQXVCLEdBQUcsQ0FBQyxDQUFDO1lBRWhDLEtBQUssTUFBTSxNQUFNLElBQUksT0FBTyxFQUFFLENBQUM7Z0JBQzdCLElBQUksY0FBYyxJQUFJLENBQUM7b0JBQUUsTUFBTTtnQkFFL0IsTUFBTSxlQUFlLEdBQUcsTUFBTSxDQUFDLGdCQUFnQixDQUFDO2dCQUVoRCxJQUFJLGVBQWUsSUFBSSxDQUFDLEVBQUUsQ0FBQztvQkFDekIsU0FBUyxDQUFDLHdCQUF3QjtnQkFDcEMsQ0FBQztnQkFFRCxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLGNBQWMsRUFBRSxlQUFlLENBQUMsQ0FBQztnQkFFNUQsd0NBQXdDO2dCQUN4QyxNQUFNLGlCQUFpQixHQUFHLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQztnQkFDN0MsSUFBSSxpQkFBaUIsRUFBRSxDQUFDO29CQUN0Qix1QkFBdUIsSUFBSSxTQUFTLENBQUM7Z0JBQ3ZDLENBQUM7Z0JBRUQsTUFBTSxDQUFDLGdCQUFnQixHQUFHLGVBQWUsR0FBRyxTQUFTLENBQUM7Z0JBQ3RELGNBQWMsSUFBSSxTQUFTLENBQUM7Z0JBRTVCLElBQUksTUFBTSxDQUFDLGdCQUFnQixLQUFLLENBQUMsRUFBRSxDQUFDO29CQUNsQyxNQUFNLENBQUMsTUFBTSxHQUFHLFVBQVUsQ0FBQztnQkFDN0IsQ0FBQztnQkFDRCxjQUFjLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUU1QixNQUFNLFdBQVcsR0FBMkI7b0JBQzFDLDZCQUE2QixFQUFFLE1BQU0sQ0FBQyxFQUFFO29CQUN4QyxpQkFBaUIsRUFBRSxNQUFNLENBQUMsaUJBQWlCO29CQUMzQyxXQUFXLEVBQUUsU0FBUztvQkFDdEIsSUFBSSxFQUFFLE9BQU87b0JBQ2IsTUFBTSxFQUFFLFFBQVE7b0JBQ2hCLGNBQWMsRUFBRSxNQUFNLENBQUMsY0FBYztvQkFDckMsV0FBVyxFQUFFLE1BQU0sQ0FBQyxXQUFXO29CQUMvQixRQUFRLEVBQUUsUUFBUSxJQUFJLElBQUk7b0JBQzFCLGdCQUFnQixFQUFFLGdCQUFnQixJQUFJLElBQUk7b0JBQzFDLHNCQUFzQixFQUFFLGNBQWM7aUJBQ3ZDLENBQUM7Z0JBRUYsT0FBTyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztZQUM1QixDQUFDO1lBRUQsTUFBTSxJQUFJLENBQUMsMEJBQTBCLENBQUMsT0FBYyxFQUFFLGFBQWEsQ0FBQyxDQUFDO1lBQ3JFLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUFDLGNBQXFCLEVBQUUsYUFBYSxDQUFDLENBQUM7WUFFMUUsT0FBTyxFQUFFLHVCQUF1QixFQUFFLENBQUM7UUFDckMsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7OztPQWVHO0lBQ0gsS0FBSyxDQUFDLFNBQVMsQ0FBQyxVQUFrQjtRQUNoQyxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUM7UUFFckUsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ1osT0FBTyxDQUFDLENBQUM7UUFDWCxDQUFDO1FBRUQsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUM7WUFDaEQsTUFBTSxFQUFFLFFBQVE7WUFDaEIsaUJBQWlCLEVBQUUsTUFBTSxDQUFDLEVBQUU7U0FDN0IsQ0FBQyxDQUFDO1FBRUgsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFNUMsSUFBSSxNQUFNLENBQUMsTUFBTSxLQUFLLE1BQU0sRUFBRSxDQUFDO1lBQzdCLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUNmLGtDQUFrQyxNQUFNLENBQUMsRUFBRSxjQUFjLFVBQVUsSUFBSTtnQkFDckUsT0FBTyxNQUFNLENBQUMsTUFBTSxtQ0FBbUMsTUFBTSxJQUFJO2dCQUNqRSw0Q0FBNEMsQ0FDL0MsQ0FBQztZQUVGLE1BQU0sSUFBSSxDQUFDLHFCQUFxQixDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUM5QyxDQUFDO1FBRUQsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVELEtBQUssQ0FBQyxxQkFBcUIsQ0FDekIsVUFBa0IsRUFDbEIsVUFVSSxFQUFFO1FBRU4sTUFBTSxFQUFFLElBQUksR0FBRyxFQUFFLEVBQUUsSUFBSSxHQUFHLENBQUMsRUFBRSxJQUFJLEVBQUUsYUFBYSxFQUFFLEdBQUcsT0FBTyxDQUFDO1FBRTdELE1BQU0sS0FBSyxHQUFRO1lBQ2pCLFdBQVcsRUFBRSxVQUFVO1NBQ3hCLENBQUM7UUFFRixJQUFJLElBQUksRUFBRSxDQUFDO1lBQ1QsS0FBSyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7UUFDcEIsQ0FBQztRQUVELElBQUksYUFBYSxFQUFFLENBQUM7WUFDbEIsS0FBSyxDQUFDLGNBQWMsR0FBRyxhQUFhLENBQUM7UUFDdkMsQ0FBQztRQUVELE9BQU8sTUFBTSxJQUFJLENBQUMsdUJBQXVCLENBQUMsS0FBSyxFQUFFO1lBQy9DLElBQUk7WUFDSixJQUFJO1lBQ0osS0FBSyxFQUFFLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRTtTQUM5QixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQsS0FBSyxDQUFDLHlCQUF5QixDQUFDLE1BQWM7UUFDNUMsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDeEMsTUFBTSxFQUFFLGdCQUFnQixFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBRXZDLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsZ0JBQWdCLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUM1RCxJQUFJLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUNmLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLDJCQUEyQixDQUM1QixDQUFDO1FBQ0osQ0FBQztRQUVELE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFFRCxLQUFLLENBQUMsa0NBQWtDLENBQ3RDLEtBQWUsRUFDZixVQUFrQjtRQUVsQixNQUFNLEVBQ0osY0FBYyxFQUNkLG1CQUFtQixFQUNuQixrQkFBa0IsRUFDbEIsZUFBZSxFQUNmLFdBQVcsR0FDWixHQUFHLEtBQUssQ0FBQztRQUVWLElBQUksZUFBZSxJQUFJLFVBQVUsR0FBRyxlQUFlLEVBQUUsQ0FBQztZQUNwRCxPQUFPLEVBQUUsV0FBVyxFQUFFLENBQUMsRUFBRSxVQUFVLEVBQUUsU0FBUyxFQUFFLENBQUM7UUFDbkQsQ0FBQztRQUVELElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztZQUNwQixPQUFPLEVBQUUsV0FBVyxFQUFFLENBQUMsRUFBRSxVQUFVLEVBQUUsU0FBUyxFQUFFLENBQUM7UUFDbkQsQ0FBQztRQUVELHNDQUFzQztRQUN0QyxNQUFNLFVBQVUsR0FBRyxXQUFXO1lBQzVCLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsV0FBVyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztZQUMxRCxDQUFDLENBQUMsU0FBUyxDQUFDO1FBRWQsSUFBSSxTQUFTLEdBQ1gsbUJBQW1CLEtBQUssWUFBWTtZQUNsQyxDQUFDLENBQUMsVUFBVSxHQUFHLENBQUMsY0FBYyxHQUFHLEdBQUcsQ0FBQztZQUNyQyxDQUFDLENBQUMsY0FBYyxDQUFDO1FBRXJCLElBQUksa0JBQWtCLEVBQUUsQ0FBQztZQUN2QixTQUFTLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxTQUFTLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztRQUN0RCxDQUFDO1FBRUQsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUV6QyxPQUFPLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxDQUFDO0lBQ3JDLENBQUM7SUFFRCxLQUFLLENBQUMscUJBQXFCLENBQ3pCLFNBQWlCLEVBQ2pCLGVBQXVCLEVBQ3ZCLFVBQWtCO1FBRWxCLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxNQUFNLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUUxQyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixNQUFNLElBQUksbUJBQVcsQ0FDbkIsbUJBQVcsQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUM5QixpQ0FBaUMsQ0FDbEMsQ0FBQztRQUNKLENBQUM7UUFFRCxNQUFNLEVBQ0osbUJBQW1CLEVBQ25CLGVBQWUsRUFDZixlQUFlLEVBQ2Ysb0JBQW9CLEVBQ3BCLGdCQUFnQixHQUNqQixHQUFHLE1BQU0sQ0FBQztRQUVYLElBQUksVUFBVSxHQUFHLGVBQWUsRUFBRSxDQUFDO1lBQ2pDLE9BQU8sQ0FBQyxDQUFDO1FBQ1gsQ0FBQztRQUVELElBQUksZ0JBQWdCLEdBQUcsZUFBZTtZQUNwQyxDQUFDLENBQUMsb0JBQW9CLEtBQUssWUFBWTtnQkFDckMsQ0FBQyxDQUFDLFNBQVMsR0FBRyxDQUFDLGVBQWUsR0FBRyxHQUFHLENBQUM7Z0JBQ3JDLENBQUMsQ0FBQyxlQUFlO1lBQ25CLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFTixJQUFJLG1CQUFtQixFQUFFLENBQUM7WUFDeEIsZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsRUFBRSxtQkFBbUIsQ0FBQyxDQUFDO1FBQ3JFLENBQUM7UUFFRCxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBRXRFLElBQUksZUFBZSxHQUFHLE1BQU0sRUFBRSxDQUFDO1lBQzdCLE1BQU0sTUFBTSxHQUFHLElBQUEsa0NBQXlCLEVBQ3RDLGVBQWUsRUFDZixnQkFBZ0IsQ0FDakIsQ0FBQztZQUNGLElBQUksTUFBTSxJQUFJLENBQUMsRUFBRSxDQUFDO2dCQUNoQixNQUFNLElBQUksbUJBQVcsQ0FDbkIsbUJBQVcsQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUM5QixxQkFBcUIsQ0FDdEIsQ0FBQztZQUNKLENBQUM7WUFDRCxPQUFPLE1BQU0sQ0FBQztRQUNoQixDQUFDO1FBRUQsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLGdCQUFnQixDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUVELEtBQUssQ0FBQyxjQUFjLENBQUMsVUFBa0I7UUFDckMsSUFBSSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUMsVUFBVSxDQUFDLENBQUM7UUFFN0QsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsZUFBZSxDQUFDO1lBQzFDLFdBQVcsRUFBRSxRQUFRLENBQUMsRUFBRTtZQUN4QixhQUFhLEVBQUUsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUU7U0FDL0IsQ0FBQyxDQUFDO1FBRUgsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztJQUVLLEFBQU4sS0FBSyxDQUFDLHNCQUFzQixDQUMxQixVQUFrQixFQUNELGFBQXNDO1FBRXZELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FDbkM7WUFDRSxXQUFXLEVBQUUsVUFBVTtTQUN4QixFQUNELFNBQVMsRUFDVCxhQUFhLENBQ2QsQ0FBQztRQUVGLElBQUksTUFBTSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUN4QixPQUFPLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FDN0I7Z0JBQ0UsV0FBVyxFQUFFLFVBQVU7Z0JBQ3ZCLE1BQU0sRUFBRSxDQUFDO2FBQ1YsRUFDRCxTQUFTLEVBQ1QsYUFBYSxDQUNkLENBQUM7UUFDSixDQUFDO1FBRUQsT0FBTyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbkIsQ0FBQztJQUVELEtBQUssQ0FBQyw4QkFBOEIsQ0FDbEMsS0FBcUMsRUFDckMsUUFBMkMsRUFDM0MsTUFBYztRQUVkLE1BQU0sRUFDSix1QkFBdUIsRUFDdkIsd0JBQXdCLEVBQ3hCLDRCQUE0QixFQUM1Qiw2QkFBNkIsRUFDN0IsV0FBVyxHQUNaLEdBQUcsS0FBSyxDQUFDO1FBRVYsSUFBSSxhQUFhLEdBQ2YsNEJBQTRCLEtBQUssWUFBWSxJQUFJLHVCQUF1QjtZQUN0RSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsdUJBQXVCLEdBQUcsR0FBRyxDQUFDO1lBQzFDLENBQUMsQ0FBQyx1QkFBdUIsQ0FBQztRQUU5QixJQUFJLGNBQWMsR0FDaEIsNkJBQTZCLEtBQUssWUFBWSxJQUFJLHdCQUF3QjtZQUN4RSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsd0JBQXdCLEdBQUcsR0FBRyxDQUFDO1lBQzNDLENBQUMsQ0FBQyx3QkFBd0IsQ0FBQztRQUMvQixPQUFPO1lBQ0wsT0FBTyxFQUFFO2dCQUNQLEtBQUssRUFBRSxhQUFhLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBRXBELFVBQVUsRUFBRSxXQUFXO29CQUNyQixDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLFdBQVcsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUM7b0JBQzFELENBQUMsQ0FBQyxJQUFJO2FBQ1Q7WUFDRCxRQUFRLEVBQUU7Z0JBQ1IsS0FBSyxFQUFFLGNBQWMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDdEQsV0FBVyxFQUFFLFFBQVEsQ0FBQyxXQUFXO2dCQUNqQyxVQUFVLEVBQUUsV0FBVztvQkFDckIsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxXQUFXLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDO29CQUMxRCxDQUFDLENBQUMsSUFBSTthQUNUO1NBQ0YsQ0FBQztJQUNKLENBQUM7SUFFRCxLQUFLLENBQUMsdUJBQXVCLENBQUMsVUFBa0IsRUFBRSxZQUFvQjtRQUNwRSxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUM3RCxNQUFNLGdCQUFnQixHQUFHLE1BQU0sRUFBRSxjQUFjLElBQUksRUFBRSxDQUFDO1FBRXRELE1BQU0sS0FBSyxHQUFHLENBQUMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLEdBQUcsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FDcEUsT0FBTyxDQUNSLENBQUM7UUFFRixNQUFNLElBQUksQ0FBQyxhQUFhLENBQUM7WUFDdkIsRUFBRSxFQUFFLE1BQU0sQ0FBQyxFQUFFO1lBQ2IsY0FBYyxFQUFFLEtBQUs7U0FDdEIsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUdlLEFBQU4sS0FBSyxDQUFDLGtCQUFrQixDQUNoQyxNQUFrRCxFQUNqQyxhQUFzQztRQUV2RCxJQUFJLENBQUM7WUFDSCwwRUFBMEU7WUFDMUUsdUVBQXVFO1lBQ3ZFLHdFQUF3RTtZQUN4RSx5RUFBeUU7WUFDekUsb0VBQW9FO1lBQ3BFLHlFQUF5RTtZQUN6RSxvRUFBb0U7WUFDcEUsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMseUJBQXlCLENBQ2xELE1BQU0sQ0FBQyxFQUFFLEVBQ1QsU0FBUyxFQUNULGFBQWEsQ0FDZCxDQUFDO1lBRUYsSUFBSSxDQUFDLE9BQU8sSUFBSSxPQUFPLENBQUMsTUFBTSxLQUFLLFFBQVEsRUFBRSxDQUFDO2dCQUM1QyxPQUFPO1lBQ1QsQ0FBQztZQUVELE1BQU0sRUFBRSxzQkFBc0IsRUFBRSxnQkFBZ0IsRUFBRSxHQUFHLE9BQU8sQ0FBQztZQUU3RCxJQUFJLGdCQUFnQixJQUFJLENBQUMsRUFBRSxDQUFDO2dCQUMxQix5RUFBeUU7Z0JBQ3pFLHlDQUF5QztnQkFDekMsTUFBTSxJQUFJLENBQUMsd0JBQXdCLENBQ2pDLEVBQUUsRUFBRSxFQUFFLE9BQU8sQ0FBQyxFQUFFLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBUyxFQUM1QyxhQUFhLENBQ2QsQ0FBQztnQkFDRixPQUFPO1lBQ1QsQ0FBQztZQUVELE1BQU0sV0FBVyxHQUFHLE1BQU0sSUFBSSxDQUFDLDBCQUEwQixDQUN2RCxzQkFBc0IsRUFDdEIsU0FBUyxFQUNULGFBQWEsQ0FDZCxDQUFDO1lBRUYsa0RBQWtEO1lBQ2xELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FDdEMsV0FBVyxDQUFDLGlCQUFpQixFQUM3QixTQUFTLEVBQ1QsYUFBYSxDQUNkLENBQUM7WUFFRixNQUFNLGtCQUFrQixHQUFHO2dCQUN6QixJQUFJLEVBQUUsT0FBTztnQkFDYixXQUFXLEVBQUUsZ0JBQWdCO2dCQUM3QixjQUFjLEVBQUUsU0FBUztnQkFDekIsTUFBTSxFQUFFLGdCQUFnQjtnQkFDeEIsaUJBQWlCLEVBQUUsV0FBVyxDQUFDLGlCQUFpQjtnQkFDaEQsV0FBVyxFQUFFLFdBQVcsQ0FBQyxXQUFXO2dCQUNwQyxlQUFlLEVBQUUsTUFBTSxDQUFDLE1BQU0sR0FBRyxnQkFBZ0I7Z0JBQ2pELGdCQUFnQixFQUFFLFdBQVcsQ0FBQyxnQkFBZ0I7Z0JBQzlDLFFBQVEsRUFBRSxJQUFJO2FBQ2YsQ0FBQztZQUVGLE1BQU0sSUFBSSxDQUFDLHlCQUF5QixDQUNsQyxrQkFBeUIsRUFDekIsYUFBYSxDQUNkLENBQUM7WUFFRixzRUFBc0U7WUFDdEUsc0RBQXNEO1lBQ3RELE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUNqQztnQkFDRSxFQUFFLEVBQUUsT0FBTyxDQUFDLEVBQUU7Z0JBQ2QsTUFBTSxFQUFFLFNBQVM7Z0JBQ2pCLGdCQUFnQixFQUFFLENBQUM7YUFDYixFQUNSLGFBQWEsQ0FDZCxDQUFDO1lBQ0YsTUFBTSxJQUFJLENBQUMsMEJBQTBCLENBQ25DO2dCQUNFLDZCQUE2QixFQUFFLE9BQU8sQ0FBQyxFQUFFO2dCQUN6QyxpQkFBaUIsRUFBRSxPQUFPLENBQUMsaUJBQWlCO2dCQUM1QyxXQUFXLEVBQUUsZ0JBQWdCO2dCQUM3QixJQUFJLEVBQUUsT0FBTztnQkFDYixNQUFNLEVBQUUsUUFBUTtnQkFDaEIsY0FBYyxFQUFFLFNBQVM7Z0JBQ3pCLFdBQVcsRUFBRSxXQUFXLENBQUMsV0FBVztnQkFDcEMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxRQUFRO2dCQUM5QixnQkFBZ0IsRUFBRSxXQUFXLENBQUMsZ0JBQWdCO2FBQy9DLEVBQ0QsYUFBYSxDQUNkLENBQUM7UUFDSixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLDZCQUE2QixFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQ3pELE1BQU0sS0FBSyxDQUFDO1FBQ2QsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7OztPQU9HO0lBQ0ssS0FBSyxDQUFDLFdBQVcsQ0FDdkIsS0FBYyxFQUNkLElBQXVDLEVBQ3ZDLE9BQWtDLEVBQ2xDLFNBQVMsR0FBRyxFQUFFO1FBRWQsTUFBTSxPQUFPLEdBQWMsRUFBRSxDQUFDO1FBRTlCLEtBQUssSUFBSSxLQUFLLEdBQUcsQ0FBQyxFQUFFLEtBQUssR0FBRyxLQUFLLENBQUMsTUFBTSxFQUFFLEtBQUssSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUM3RCxNQUFNLE9BQU8sR0FBRyxNQUFNLE9BQU8sQ0FBQyxVQUFVLENBQ3RDLEtBQUssQ0FBQyxLQUFLLENBQUMsS0FBSyxFQUFFLEtBQUssR0FBRyxTQUFTLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQ2hELENBQUM7WUFFRixLQUFLLE1BQU0sT0FBTyxJQUFJLE9BQU8sRUFBRSxDQUFDO2dCQUM5QixJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssV0FBVyxFQUFFLENBQUM7b0JBQ25DLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUM5QixDQUFDO3FCQUFNLENBQUM7b0JBQ04sT0FBTyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDMUIsQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDO1FBRUQsT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7O09BYUc7SUFDSCxLQUFLLENBQUMsMEJBQTBCLENBQzlCLE9BQXFEO1FBRXJELElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDcEIsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLGNBQWMsR0FBYyxFQUFFLENBQUM7UUFDckMsTUFBTSxpQkFBaUIsR0FBYyxFQUFFLENBQUM7UUFFeEMsMEVBQTBFO1FBQzFFLHlFQUF5RTtRQUN6RSx3RUFBd0U7UUFDeEUsMkVBQTJFO1FBQzNFLHdFQUF3RTtRQUN4RSxtRUFBbUU7UUFDbkUsTUFBTSxhQUFhLEdBQUc7WUFDcEIsR0FBRyxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRTtTQUNsRSxDQUFDO1FBRUYsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUNwQyxhQUFhLEVBQ2IsS0FBSyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQ2YsTUFBTSxJQUFJLENBQUMsa0JBQWtCLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDdEMsT0FBTyxNQUFNLENBQUMsaUJBQWlCLENBQUM7UUFDbEMsQ0FBQyxFQUNELENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUN4QyxDQUFDO1FBRUYsTUFBTSxTQUFTLEdBQUcsQ0FBQyxHQUFHLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7UUFFeEMsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUNwQixTQUFTLEVBQ1QsQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxRQUFRLENBQUMsRUFDbEQsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLGlCQUFpQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FDM0MsQ0FBQztRQUVGLHVFQUF1RTtRQUN2RSw2REFBNkQ7UUFDN0QsS0FBSyxNQUFNLENBQUMsS0FBSyxFQUFFLFFBQVEsQ0FBQyxJQUFJO1lBQzlCLENBQUMsaUJBQWlCLEVBQUUsY0FBYyxDQUFDO1lBQ25DLENBQUMsd0JBQXdCLEVBQUUsaUJBQWlCLENBQUM7U0FDckMsRUFBRSxDQUFDO1lBQ1gsSUFBSSxRQUFRLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQ3BCLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUNoQiwyQkFBMkIsUUFBUSxDQUFDLE1BQU0sSUFBSSxLQUFLLFdBQVc7b0JBQzVELGdCQUFpQixRQUFRLENBQUMsQ0FBQyxDQUFXLEVBQUUsT0FBTyxJQUFJLFFBQVEsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUNuRSxDQUFDO1lBQ0osQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBR0ssQUFBTixLQUFLLENBQUMsdUJBQXVCLENBQzNCLFdBQW1ELEVBQ2xDLGFBQXNDO1FBRXZELE1BQU0sU0FBUyxHQUFHLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUNuRDtZQUNFLHNCQUFzQixFQUFFLFdBQVcsQ0FBQyxFQUFFO1NBQ3ZDLEVBQ0QsU0FBUyxFQUNULGFBQWEsQ0FDZCxDQUFDO1FBQ0YsSUFBSSxTQUFTLENBQUMsTUFBTSxLQUFLLENBQUM7WUFDeEIsTUFBTSxJQUFJLG1CQUFXLENBQUMsbUJBQVcsQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUFFLGtCQUFrQixDQUFDLENBQUM7UUFFNUUsMkVBQTJFO1FBQzNFLHdFQUF3RTtRQUN4RSx3RUFBd0U7UUFDeEUsTUFBTSxTQUFTLEdBQUc7WUFDaEIsR0FBRyxJQUFJLEdBQUcsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsNkJBQTZCLENBQUMsQ0FBQztTQUNsRSxDQUFDO1FBRUYsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQy9DLEVBQUUsRUFBRSxFQUFFLFNBQVMsRUFBRSxFQUNqQixTQUFTLEVBQ1QsYUFBYSxDQUNkLENBQUM7UUFFRixNQUFNLFdBQVcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBRTFFLE1BQU0sUUFBUSxHQUFHLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRTtZQUNuQyxNQUFNLE1BQU0sR0FBRyxXQUFXLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyw2QkFBNkIsQ0FBQyxDQUFDO1lBRWhFLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztnQkFDWixNQUFNLElBQUksbUJBQVcsQ0FDbkIsbUJBQVcsQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUM5QixpQkFBaUIsQ0FDbEIsQ0FBQztZQUNKLENBQUM7WUFFRCx5RUFBeUU7WUFDekUscURBQXFEO1lBQ3JELE1BQU0sQ0FBQyxnQkFBZ0IsSUFBSSxDQUFDLENBQUMsV0FBVyxDQUFDO1lBRXpDLE9BQU8sTUFBTSxDQUFDO1FBQ2hCLENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxJQUFJLENBQUMsMEJBQTBCLENBQ25DLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLENBQUMsQ0FBUSxFQUMvRCxhQUFhLENBQ2QsQ0FBQztRQUVGLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUNqQyxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDdEMsRUFBRSxFQUFFLE1BQU0sQ0FBQyxFQUFFO1lBQ2IsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDLGdCQUFnQjtZQUN6QyxNQUFNLEVBQUUsUUFBUTtTQUNqQixDQUFDLENBQVEsRUFDVixhQUFhLENBQ2QsQ0FBQztJQUNKLENBQUM7SUFHSyxBQUFOLEtBQUssQ0FBQyx3QkFBd0IsQ0FDNUIsV0FBbUQsRUFDbEMsYUFBc0M7UUFFdkQsTUFBTSxDQUFDLE1BQU0sQ0FBQyxHQUFHLE1BQU0sSUFBSSxDQUFDLHNCQUFzQixDQUNoRDtZQUNFLHNCQUFzQixFQUFFLFdBQVcsQ0FBQyxFQUFFO1lBQ3RDLE1BQU0sRUFBRSxRQUFRO1NBQ2pCLEVBQ0QsU0FBUyxFQUNULGFBQWEsQ0FDZCxDQUFDO1FBRUYsMEVBQTBFO1FBQzFFLHVFQUF1RTtRQUN2RSwwRUFBMEU7UUFDMUUsNEVBQTRFO1FBQzVFLDBFQUEwRTtRQUMxRSw4QkFBOEI7UUFDOUIsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ1osT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLGVBQWUsR0FBRyxNQUFNLENBQUMsZ0JBQWdCLENBQUM7UUFFaEQsSUFBSSxlQUFlLEtBQUssQ0FBQztZQUFFLE9BQU87UUFFbEMsZ0JBQWdCO1FBQ2hCLE1BQU0sSUFBSSxDQUFDLDBCQUEwQixDQUNuQztZQUNFLDZCQUE2QixFQUFFLE1BQU0sQ0FBQyxFQUFFO1lBQ3hDLFdBQVcsRUFBRSxlQUFlO1lBQzVCLElBQUksRUFBRSxPQUFPO1lBQ2IsTUFBTSxFQUFFLFFBQVE7WUFDaEIsY0FBYyxFQUFFLGNBQWM7WUFDOUIsV0FBVyxFQUFFLFdBQVcsQ0FBQyxXQUFXO1lBQ3BDLFFBQVEsRUFBRSxXQUFXLENBQUMsUUFBUTtZQUM5QixnQkFBZ0IsRUFBRSxXQUFXLENBQUMsZ0JBQWdCO1lBQzlDLGlCQUFpQixFQUFFLFdBQVcsQ0FBQyxpQkFBaUI7U0FDMUMsRUFDUixhQUFhLENBQ2QsQ0FBQztRQUVGLDJDQUEyQztRQUMzQyxNQUFNLElBQUksQ0FBQyx3QkFBd0IsQ0FDakM7WUFDRSxFQUFFLEVBQUUsTUFBTSxDQUFDLEVBQUU7WUFDYixNQUFNLEVBQUUsVUFBVTtZQUNsQixnQkFBZ0IsRUFBRSxDQUFDO1NBQ3BCLEVBQ0QsYUFBYSxDQUNkLENBQUM7SUFDSixDQUFDO0lBR0ssQUFBTixLQUFLLENBQUMsa0JBQWtCLENBQ3RCLFdBQW1ELEVBQ2xDLGFBQXNDO1FBRXZELGtEQUFrRDtRQUNsRCxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQ3RDLFdBQVcsQ0FBQyxpQkFBaUIsRUFDN0IsU0FBUyxFQUNULGFBQWEsQ0FDZCxDQUFDO1FBRUYscURBQXFEO1FBQ3JELE1BQU0sZUFBZSxHQUFHLFdBQVcsQ0FBQyxJQUFJLEtBQUssT0FBTyxDQUFDO1FBQ3JELE1BQU0sZUFBZSxHQUFHLGVBQWU7WUFDckMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQywyQkFBMkI7WUFDaEUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQyxDQUFDLDJCQUEyQjtRQUVuRSw0QkFBNEI7UUFDNUIsTUFBTSxRQUFRLEdBQUc7WUFDZixJQUFJLEVBQUUsV0FBVyxDQUFDLElBQUksS0FBSyxPQUFPLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsT0FBTztZQUN2RCxXQUFXLEVBQUUsaUJBQWlCO1lBQzlCLGNBQWMsRUFBRSxjQUFjO1lBQzlCLE1BQU0sRUFBRSxXQUFXLENBQUMsTUFBTTtZQUMxQixpQkFBaUIsRUFBRSxXQUFXLENBQUMsaUJBQWlCO1lBQ2hELFdBQVcsRUFBRSxXQUFXLENBQUMsV0FBVztZQUNwQyxRQUFRLEVBQUUsV0FBVyxDQUFDLFFBQVE7WUFDOUIsZ0JBQWdCLEVBQUUsV0FBVyxDQUFDLGdCQUFnQjtZQUM5QyxlQUFlO1lBQ2YsUUFBUSxFQUFFLElBQUk7U0FDZixDQUFDO1FBQ0YsTUFBTSxJQUFJLENBQUMseUJBQXlCLENBQUMsUUFBZSxFQUFFLGFBQWEsQ0FBQyxDQUFDO1FBRXJFLDJCQUEyQjtRQUMzQixNQUFNLElBQUksQ0FBQyx5QkFBeUIsQ0FDbEMsRUFBRSxFQUFFLEVBQUUsV0FBVyxDQUFDLEVBQUUsRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLEVBQzFDLGFBQWEsQ0FDZCxDQUFDO0lBQ0osQ0FBQztJQUdLLEFBQU4sS0FBSyxDQUFDLHdCQUF3QixDQUM1QixZQUFzRCxFQUNyQyxhQUFzQztRQUV2RCxNQUFNLGlCQUFpQixHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7UUFFNUMsS0FBSyxNQUFNLFdBQVcsSUFBSSxZQUFZLEVBQUUsQ0FBQztZQUN2QyxJQUFJLFdBQVcsQ0FBQyxJQUFJLEtBQUssT0FBTyxFQUFFLENBQUM7Z0JBQ2pDLE1BQU0sSUFBSSxDQUFDLHVCQUF1QixDQUFDLFdBQVcsRUFBRSxhQUFhLENBQUMsQ0FBQztZQUNqRSxDQUFDO2lCQUFNLENBQUM7Z0JBQ04sTUFBTSxJQUFJLENBQUMsd0JBQXdCLENBQUMsV0FBVyxFQUFFLGFBQWEsQ0FBQyxDQUFDO1lBQ2xFLENBQUM7WUFDRCxNQUFNLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxXQUFXLEVBQUUsYUFBYSxDQUFDLENBQUM7WUFDMUQsaUJBQWlCLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1FBQ3ZELENBQUM7UUFFRCw0RUFBNEU7UUFDNUUsdUVBQXVFO1FBQ3ZFLHlFQUF5RTtRQUN6RSxxRUFBcUU7UUFDckUsNkJBQTZCO1FBQzdCLEtBQUssTUFBTSxRQUFRLElBQUksaUJBQWlCLEVBQUUsQ0FBQztZQUN6QyxNQUFNLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxRQUFRLEVBQUUsYUFBYSxDQUFDLENBQUM7UUFDNUQsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7Ozs7T0FRRztJQUNHLEFBQU4sS0FBSyxDQUFDLHFCQUFxQixDQUN6QixRQUFnQixFQUNDLGFBQXNDO1FBRXZELE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLHNCQUFzQixDQUMvQztZQUNFLGlCQUFpQixFQUFFLFFBQVE7WUFDM0IsTUFBTSxFQUFFLFFBQVE7U0FDakIsRUFDRCxTQUFTLEVBQ1QsYUFBYSxDQUNkLENBQUM7UUFFRixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRXhDLE1BQU0sSUFBSSxDQUFDLGFBQWEsQ0FDdEI7WUFDRSxFQUFFLEVBQUUsUUFBUTtZQUNaLE1BQU0sRUFBRSxNQUFNLENBQUMsTUFBTTtZQUNyQixnQkFBZ0IsRUFBRSxNQUFNLENBQUMsZ0JBQWdCO1NBQzFDLEVBQ0QsYUFBYSxDQUNkLENBQUM7UUFFRixPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNLLFVBQVUsQ0FDaEIsT0FBcUQ7UUFFckQsSUFBSSxNQUFNLEdBQUcsQ0FBQyxDQUFDO1FBQ2YsSUFBSSxnQkFBZ0IsR0FBRyxDQUFDLENBQUM7UUFFekIsS0FBSyxNQUFNLE1BQU0sSUFBSSxPQUFPLEVBQUUsQ0FBQztZQUM3QixNQUFNLElBQUksTUFBTSxDQUFDLGdCQUFnQixDQUFDO1lBRWxDLElBQUksQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFLENBQUM7Z0JBQ3ZCLGdCQUFnQixJQUFJLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQztZQUM5QyxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sRUFBRSxNQUFNLEVBQUUsZ0JBQWdCLEVBQUUsQ0FBQztJQUN0QyxDQUFDO0lBRUQsS0FBSyxDQUFDLGVBQWUsQ0FBQyxTQUE4QztRQUNsRSxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3RCLE9BQU87UUFDVCxDQUFDO1FBRUQsd0VBQXdFO1FBQ3hFLGdFQUFnRTtRQUNoRSxNQUFNLElBQUksQ0FBQyxlQUFlLENBQ3hCLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDM0IsRUFBRSxFQUFFLFFBQVEsQ0FBQyxFQUFFO1lBQ2YsTUFBTSxFQUFFLFNBQWtCO1NBQzNCLENBQUMsQ0FBQyxDQUNKLENBQUM7SUFDSixDQUFDO0lBRUQsS0FBSyxDQUFDLGtCQUFrQixDQUFDLEVBQ3ZCLE9BQU8sRUFDUCxRQUFRLEVBQ1IsYUFBYSxFQUNiLGNBQWMsRUFDZCxhQUFhLEVBQ2IsVUFBVSxFQUNWLFFBQVEsRUFDUixnQkFBZ0IsRUFDaEIsUUFBUSxHQVdUO1FBQ0MsTUFBTSxjQUFjLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDbkUsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDakUsSUFBSSxhQUFhLEVBQUUsQ0FBQztZQUNsQixJQUFJLGFBQWEsSUFBSSxhQUFhLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQ3ZDLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQztvQkFDbkIsV0FBVyxFQUFFLE9BQU87b0JBQ3BCLE1BQU0sRUFBRSxhQUFhO29CQUNyQixXQUFXLEVBQUUsUUFBUTt3QkFDbkIsQ0FBQyxDQUFDLDRCQUE0QixRQUFRLEVBQUU7d0JBQ3hDLENBQUMsQ0FBQyxnQkFBZ0I7b0JBQ3BCLFVBQVU7b0JBQ1YsUUFBUTtvQkFDUixnQkFBZ0I7b0JBQ2hCLFFBQVEsRUFBRSxjQUFjLENBQUMsRUFBRTtvQkFDM0IsYUFBYSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLFVBQVU7b0JBQ3ZELFFBQVE7aUJBQ1QsQ0FBQyxDQUFDO2dCQUVILE1BQU0sSUFBSSxDQUFDLHVCQUF1QixDQUFDLE9BQU8sRUFBRSxhQUFhLENBQUMsQ0FBQztZQUM3RCxDQUFDO1lBRUQsSUFBSSxjQUFjLElBQUksY0FBYyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUN6QyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUM7b0JBQ25CLFdBQVcsRUFBRSxRQUFRO29CQUNyQixNQUFNLEVBQUUsY0FBYztvQkFDdEIsV0FBVyxFQUFFLFFBQVE7d0JBQ25CLENBQUMsQ0FBQyw0QkFBNEIsUUFBUSxFQUFFO3dCQUN4QyxDQUFDLENBQUMsZ0JBQWdCO29CQUNwQixVQUFVO29CQUNWLFFBQVE7b0JBQ1IsZ0JBQWdCO29CQUNoQixPQUFPLEVBQUUsYUFBYSxDQUFDLEVBQUU7b0JBQ3pCLGFBQWEsRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxVQUFVO29CQUN2RCxRQUFRO2lCQUNULENBQUMsQ0FBQztZQUNMLENBQUM7UUFDSCxDQUFDO1FBRUQsT0FBTztZQUNMLE9BQU8sRUFBRTtnQkFDUCxLQUFLLEVBQUUsYUFBYTtnQkFDcEIsVUFBVTthQUNYO1lBQ0QsUUFBUSxFQUFFO2dCQUNSLEtBQUssRUFBRSxjQUFjO2dCQUNyQixVQUFVO2FBQ1g7U0FDRixDQUFDO0lBQ0osQ0FBQztJQUVELEtBQUssQ0FBQyxrQkFBa0IsQ0FBQyxVQUFrQixFQUFFLFlBQW9CO1FBQy9ELE1BQU0sQ0FBQyxRQUFRLENBQUMsR0FBRyxNQUFNLElBQUksQ0FBQyxhQUFhLENBQUM7WUFDMUMsYUFBYSxFQUFFLFlBQVk7U0FDNUIsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ2QsTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLFlBQVksRUFDOUIsb0JBQW9CLENBQ3JCLENBQUM7UUFDSixDQUFDO1FBQ0QsTUFBTSxRQUFRLEdBQUcsUUFBUSxDQUFDLFdBQVcsQ0FBQztRQUN0QyxJQUFJLFFBQVEsS0FBSyxVQUFVLEVBQUUsQ0FBQztZQUM1QixNQUFNLElBQUksbUJBQVcsQ0FDbkIsbUJBQVcsQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUM5Qiw2Q0FBNkMsQ0FDOUMsQ0FBQztRQUNKLENBQUM7UUFFRCxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDO1lBQ3BDLGFBQWEsRUFBRSx3QkFBd0I7WUFDdkMsTUFBTSxFQUFFLFFBQVE7U0FDakIsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ1gsTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLFlBQVksRUFDOUIsd0NBQXdDLENBQ3pDLENBQUM7UUFDSixDQUFDO1FBRUQsTUFBTSxFQUFFLGNBQWMsRUFBRSxhQUFhLEVBQUUsR0FBRyxLQUFLLENBQUM7UUFFaEQsTUFBTSxHQUFHLEdBQUcsTUFBTSxJQUFJLENBQUMsa0JBQWtCLENBQUM7WUFDeEMsT0FBTyxFQUFFLFVBQVU7WUFDbkIsUUFBUSxFQUFFLFFBQVE7WUFDbEIsYUFBYSxFQUFFLGFBQWE7WUFDNUIsY0FBYyxFQUFFLGNBQWM7WUFDOUIsYUFBYSxFQUFFLFlBQVk7WUFDM0IsVUFBVSxFQUFFLEtBQUssQ0FBQyxXQUFXO2dCQUMzQixDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLEtBQUssQ0FBQyxXQUFXLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDO2dCQUNoRSxDQUFDLENBQUMsU0FBUztTQUNkLENBQUMsQ0FBQztRQUNILE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQzs7QUF6ckNNLDZCQUFRLEdBQUc7SUFDaEIsYUFBYSxFQUFFLG9CQUFXO0NBQzNCLENBQUM7QUFHYztJQURmLElBQUEsZ0NBQXdCLEdBQUU7SUFpQ3hCLFdBQUEsSUFBQSxxQkFBYSxHQUFFLENBQUE7Ozs7c0RBdUZqQjtBQUdLO0lBREwsSUFBQSxxQkFBYSxHQUFFO0lBcUJiLFdBQUEsSUFBQSxxQkFBYSxHQUFFLENBQUE7Ozs7cURBR2pCO0FBRWU7SUFEZixJQUFBLGdDQUF3QixHQUFFO0lBa0J4QixXQUFBLElBQUEscUJBQWEsR0FBRSxDQUFBOzs7O3lEQTRHakI7QUFHSztJQURMLElBQUEscUJBQWEsR0FBRTtJQWtCYixXQUFBLElBQUEscUJBQWEsR0FBRSxDQUFBOzs7O3dEQUdqQjtBQUVLO0lBTUgsV0FBQSxJQUFBLHFCQUFhLEdBQUUsQ0FBQTs7OztvRkF3RGpCO0FBd01LO0lBRUgsV0FBQSxJQUFBLHFCQUFhLEdBQUUsQ0FBQTs7OztrRUFzQmpCO0FBeURlO0lBRGYsSUFBQSxnQ0FBd0IsR0FBRTtJQUd4QixXQUFBLElBQUEscUJBQWEsR0FBRSxDQUFBOzs7OzhEQTBGakI7QUFzR0s7SUFETCxJQUFBLGdDQUF3QixHQUFFO0lBR3hCLFdBQUEsSUFBQSxxQkFBYSxHQUFFLENBQUE7Ozs7bUVBeURqQjtBQUdLO0lBREwsSUFBQSxnQ0FBd0IsR0FBRTtJQUd4QixXQUFBLElBQUEscUJBQWEsR0FBRSxDQUFBOzs7O29FQWtEakI7QUFHSztJQURMLElBQUEsZ0NBQXdCLEdBQUU7SUFHeEIsV0FBQSxJQUFBLHFCQUFhLEdBQUUsQ0FBQTs7Ozs4REFtQ2pCO0FBR0s7SUFETCxJQUFBLGdDQUF3QixHQUFFO0lBR3hCLFdBQUEsSUFBQSxxQkFBYSxHQUFFLENBQUE7Ozs7b0VBc0JqQjtBQVdLO0lBRUgsV0FBQSxJQUFBLHFCQUFhLEdBQUUsQ0FBQTs7OztpRUF1QmpCO0FBNEpILGtCQUFlLG9CQUFvQixDQUFDIn0=