How Can I Query the Parent Record Using JS - javascript

To check if paymenttype equal credit account [entity form: auto_paymenttype]
Then , if payment amount <= resit amount, it will save else > prevent save (popup message invalid: payment amount should lower than resit amount)[entity form: auto_resittype]
Hi guys, It could be great if someone could re-code and help me on this.I am new in D365 and JS. Basically, I have entities which is auto_paymenttype and auto_resittype and their parent is Payment. How can I query the parent adjustment record using JS. I have provide my current code, please help me to review it. I have try everything but so far no luck. Sorry for my unprofessional picture. But I hope you understand it and could help me to code for this situation. Thank you.
function resitApproveAmount(executionContext) {
try {
const object = {};
object.fctx = executionContext.getFormContext();
object.saveEvent = object.fctx.getEventArgs();
object.paymentamount = object.fctx.getAttribute("auto_paymentamount").getValue();
object.resitamount = object.fctx.getAttribute("auto_resitamount").getValue();
object.paymenttype = Xrm.Page.getAttribute("auto_paymenttype").getValue();
if (object.paymenttype != null) {
object.autoGUID = object.paymenttype[0].id.substring(1, 37);
}
Xrm.WebApi.retrieveMultipleRecords("auto_paymenttype", "$select=auto_name").then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
object.auto_name = result.entities[i]["auto_name"];}
if(object.auto_name == "Credit Account"){
if (object.paymenttamount >= object.resitamount) {
alert("Payment Amount cannot be more than Resit Amount.");
object.saveEvent.preventDefault();
}
else
{object.fctx.data.save();}
}
},
function (error) {
console.log(error.message);
}
);
}
catch (error) {
console.log(error);
}
}
click here for picture overview

You code implies auto_paymentamount and auto_resitamount exist on entity Payment but you ERD shows them existing on entity Resit Type.
If they are on the Payment entity then your can retrieve their values as in your question:
const paymentAmount = formContext.getAttribute("auto_paymentamount").getValue();
const resitAmount = formContext.getAttribute("auto_resitamount").getValue();
Otherwise you need to retrieve the Resit Type record related to your Payment record, using the WebApi. If this is the case I assume you have a lookup from Payment to Resit Type.
Assuming auto_paymentamount and auto_resitamount exist on entity Payment your code can be simplified to the following two solutions. Both solutions assume this function is called during the onsave event.
Solution 1 - Retrieve the related Payment Type Record
function resitApproveAmount(executionContext) {
try {
// Get the form context
const formContext = executionContext.getFormContext();
// Extract attribute values from the form
const paymentAmount = formContext.getAttribute("auto_paymentamount").getValue();
const resitAmount = formContext.getAttribute("auto_resitamount").getValue();
const paymentTypeLookup = formContext.getAttribute("auto_paymenttype").getValue();
// Exit as payment type is not set
if (!paymentTypeLookup) return;
// Extract the payment type record ID from the payment type lookup
const paymentTypeId = paymentTypeLookup[0].id.substring(1, 37);
// Retrieve a SINGLE auto_paymenttype based on lookup ID on form
Xrm.WebApi.retrieveRecord("auto_paymenttype", paymentTypeId, "$select=auto_name").then(
function (paymentType)
{
// If the payment type is credit account then check payment amount and resit amount
if (paymentType.auto_name.toLowerCase() == "Credit Account".toLowerCase())
{
if (paymentAmount >= resitAmount) {
formContext.getEventArgs().preventDefault();
Xrm.Navigation.openErrorDialog({message:"Payment Amount cannot be more than Resit Amount."})
}
}
//Otherwise do nothing
},
function (error)
{
console.log(error.message);
}
);
}
catch (error)
{
console.log(error);
}
}
Solution 2 - Hard Code the Payment Type Record ID
This can be used if you can guarantee your payment type record ID's will be the same across all environments (i.e. Payment type records are migrated using a data migration tool)
function resitApproveAmount(executionContext) {
try {
// Get the form context
const formContext = executionContext.getFormContext();
// Extract attribute values from the form
const paymentAmount = formContext.getAttribute("auto_paymentamount").getValue();
const resitAmount = formContext.getAttribute("auto_resitamount").getValue();
const paymentTypeLookup = formContext.getAttribute("auto_paymenttype").getValue();
// Exit as payment type is not set
if (!paymentTypeLookup) return;
// Extract the payment type record ID from the payment type lookup
const paymentTypeId = paymentTypeLookup[0].id.substring(1, 37);
// Exit if payment type id is not "Credit Account"
if (paymentTypeId.toLowerCase() !== "Hard Code Credit Account GUID here / retrieve environment variable") return;
if (paymentAmount >= resitAmount) {
formContext.getEventArgs().preventDefault();
Xrm.Navigation.openErrorDialog({message:"Payment Amount cannot be more than Resit Amount."})
}
}
catch (error)
{
console.log(error);
}
}
Notes
To help with questions like this in future I would recommend drawing an Entity Relationship Diagram. This helps us understand your entity model and relationships, which helps us answer questions accurately the first time.
Diagrams.net is a great way to draw an ERD for free

I've had another read of the question and think I better understand your entity model:
Like my previous answer this solution assumes this function is called during the onsave event on Payment. I use async/await and async onsave events to simplify the retrieval of Payment Type and Resit Type. Like my other answer, if you know Payment Type GUIDs will be the same across environments you can hard code the Payment Type ID, and check the ID against the lookup ID on payment, to save yourself a WebApi call.
/**
* On Save of Payment record
* #param executionContext
* #returns
*/
async function onSave(executionContext) {
try {
// Get the form context
const formContext = executionContext.getFormContext();
const paymentTypeLookup = formContext.getAttribute("auto_paymenttype").getValue();
const resitTypeLookup = formContext.getAttribute("aut_resittype").getValue();
// Exit as payment type is not set
if (!paymentTypeLookup || !resitTypeLookup) return;
// Extract related entity IDs
const paymentTypeId = paymentTypeLookup[0].id.substring(1, 37);
const resitTypeId = resitTypeLookup[0].id.substring(1, 37);
// Get related records
const [paymentTypeResult, resitTypeResult] = await Promise.all([
Xrm.WebApi.retrieveRecord("auto_paymenttype", paymentTypeId),
Xrm.WebApi.retrieveRecord("auto_resittype", resitTypeId)
]);
// Evaluate payment type and resit type and prevent save if required
if (paymentTypeResult.auto_name.toLowerCase() === "Credit Account".toLowerCase()
&& resitTypeResult.auto_paymentAmount >= resitTypeResult.auto_resitAmount)
{
formContext.getEventArgs().preventDefault();
Xrm.Navigation.openErrorDialog({message:"Payment Amount cannot be more than Resit Amount."})
}
}
catch (error)
{
console.log(error);
}
}

Related

How to fulfill orders after Stripe's checkout.session.completed event?

My problem is with Stripe's metadata object having a 500 character limit. I have a working checkout flow but my only restriction is the character limit for my cart. My cartItems object has extras and customer notes I want to include for each cart Item. With that being said, the metadata limit gets to 500 characters fast. I have read on another post here, implementing websockets into my app which would let me create the order using after listening to stripes event. How would I go about this? Any other workarounds?
let endpointSecret;
endpointSecret =
"whsec_bd73383ed0fcf9cfb27bd4929af341605ad32577dfd8825e1143425b846bb3c3";
router.post("/webhook", (request, response) => {
const sig = request.headers["stripe-signature"];
let data;
let eventType;
if (endpointSecret) {
let event;
try {
event = stripe.webhooks.constructEvent(
request.rawBody,
sig,
endpointSecret
);
} catch (err) {
response.status(400).send(`Webhook Error: ${err.message}`);
return;
}
data = event.data.object;
eventType = event.type;
} else {
data = request.body.data.object;
eventType = request.body.type;
}
// Handle the event
if (eventType === "checkout.session.completed") {
stripe.customers
.retrieve(data.customer)
.then((customer) => {
console.log("customer:", customer);
console.log("data:", data);
createOrder(customer, data);
})
.catch((err) => console.log(err.message));
}
You don't typically pass all cart information in metadata, you would typically store that information in your own database and then retrieve the necessary information based on a UUID that corresponds with that data and the order ID that you set on the Checkout Session via metadata which then gets returned via the Webhook.
To give an example of what I'm recommending above --
In your database you would have something like:
Order ID
Cart Item1
Cart Item1 Description
Cart Item2...
123456
hat
red
scarf
123457
sock
blue
Then when you create your Checkout Session you just pass your Order ID as metadata. When you receive your checkout.session.completed Webhook, that metadata will indicate the Order ID so now you have all your data necessary for fulfillment and reconciliation (and you can update your database accordingly).
Also, to clear up a misconception, with Stripe's metadata you can have 50 keys each with values up to 500 characters long.

How to get current user's business unit in Dynamics 365

How can I get business unit details of the logged-in user with javascript? I tried Xrm.Utility.getGlobalContext().userSettings but I couldn't get any information for business unit
If you write Xrm.Utility.getGlobalContext().userSettings I assume you are working with client-side javascript inside a Model-driven app.
From the userSettings you can get the userId property, it returns the GUID of the current user.
After you have this value in order to get details from the business unit of the user you need to do a retrieve request, something like this:
// get the userId
var userId = Xrm.Utility.getGlobalContext().userSettings.userId;
// remove { and } from the userId
userId = userId.replace("{", "").replace("}", "");
// Xrm.WebApi call to retrieve details of the user (fullname)
// and the name of the businessunit (name from expand)
Xrm.WebApi.online.retrieveRecord("systemuser",
userId,
"?$select=fullname&$expand=businessunitid($select=name)").then(
function success(result) {
console.log(result);
// Columns
var systemuserid = result["systemuserid"]; // Guid
var fullname = result["fullname"]; // Text
// Many To One Relationships
if (result.hasOwnProperty("businessunitid")) {
var businessunitid_name = result["businessunitid"]["name"]; // Text
}
},
function(error) {
console.log(error.message);
}
);

Script runs smoothly from start to finish, but the expected result doesn't happen

The project aims to study a new social media:
https://booyah.live/
My needs are:
1 - Collect data from profiles that follow a specific profile.
2 - My account use this data to follow the collected profiles.
3 - Among other possible options, also unfollow the profiles I follow.
The problem found in the current script:
The profile data in theory is being collected, the script runs perfectly until the end, but for some reason I can't specify, instead of following all the collected profiles, it only follows the base profile.
For example:
I want to follow all 250 profiles that follow the ID 123456
I activate the booyahGetAccounts(123456); script
In theory the end result would be my account following 250 profiles
But the end result I end up following only the 123456 profile, so the count of people I'm following is 1
Complete Project Script:
const csrf = 'MY_CSRF_TOKEN';
async function booyahGetAccounts(uid, type = 'followers', follow = 1) {
if (typeof uid !== 'undefined' && !isNaN(uid)) {
const loggedInUserID = window.localStorage?.loggedUID;
if (uid === 0) uid = loggedInUserID;
const unfollow = follow === -1;
if (unfollow) follow = 1;
if (loggedInUserID) {
if (csrf) {
async function getUserData(uid) {
const response = await fetch(`https://booyah.live/api/v3/users/${uid}`),
data = await response.json();
return data.user;
}
const loggedInUserData = await getUserData(loggedInUserID),
targetUserData = await getUserData(uid),
followUser = uid => fetch(`https://booyah.live/api/v3/users/${loggedInUserID}/followings`, { method: (unfollow ? 'DELETE' : 'POST'), headers: { 'X-CSRF-Token': csrf }, body: JSON.stringify({ followee_uid: uid, source: 43 }) }),
logSep = (data = '', usePad = 0) => typeof data === 'string' && usePad ? console.log((data ? data + ' ' : '').padEnd(50, '━')) : console.log('━'.repeat(50),data,'━'.repeat(50));
async function getList(uid, type, follow) {
const isLoggedInUser = uid === loggedInUserID;
if (isLoggedInUser && follow && !unfollow && type === 'followings') {
follow = 0;
console.warn('You alredy follow your followings. `follow` mode switched to `false`. Followings will be retrieved instead of followed.');
}
const userData = await getUserData(uid),
totalCount = userData[type.slice(0,-1)+'_count'] || 0,
totalCountStrLength = totalCount.toString().length;
if (totalCount) {
let userIDsLength = 0;
const userIDs = [],
nickname = userData.nickname,
nicknameStr = `${nickname ? ` of ${nickname}'s ${type}` : ''}`,
alreadyFollowedStr = uid => `User ID ${uid} already followed by ${loggedInUserData.nickname} (Account #${loggedInUserID})`;
async function followerFetch(cursor = 0) {
const fetched = [];
await fetch(`https://booyah.live/api/v3/users/${uid}/${type}?cursor=${cursor}&count=100`).then(res => res.json()).then(data => {
const list = data[type.slice(0,-1)+'_list'];
if (list?.length) fetched.push(...list.map(e => e.uid));
if (fetched.length) {
userIDs.push(...fetched);
userIDsLength += fetched.length;
if (follow) followUser(uid);
console.log(`${userIDsLength.toString().padStart(totalCountStrLength)} (${(userIDsLength / totalCount * 100).toFixed(4)}%)${nicknameStr} ${follow ? 'followed' : 'retrieved'}`);
if (fetched.length === 100) {
followerFetch(data.cursor);
} else {
console.log(`END REACHED. ${userIDsLength} accounts ${follow ? 'followed' : 'retrieved'}.`);
if (!follow) logSep(targetList);
}
}
});
}
await followerFetch();
return userIDs;
} else {
console.log(`This account has no ${type}.`);
}
}
logSep(`${follow ? 'Following' : 'Retrieving'} ${targetUserData.nickname}'s ${type}`, 1);
const targetList = await getList(uid, type, follow);
} else {
console.error('Missing CSRF token. Retrieve your CSRF token from the Network tab in your inspector by clicking into the Network tab item named "bug-report-claims" and then scrolling down in the associated details window to where you see "x-csrf-token". Copy its value and store it into a variable named "csrf" which this function will reference when you execute it.');
}
} else {
console.error('You do not appear to be logged in. Please log in and try again.');
}
} else {
console.error('UID not passed. Pass the UID of the profile you are targeting to this function.');
}
}
This current question is a continuation of that answer from the link:
Collect the full list of buttons to follow without having to scroll the page (DevTools Google Chrome)
Since I can't offer more bounty on that question, I created this one to offer the new bounty to anyone who can fix the bug and make the script work.
Access account on Booyah website to use for tests:
Access by google:
User: teststackoverflowbooyah#gmail.com
Password: quartodemilha
I have to admit that it is really hard to read your code, I spent a lesser amount of time rewriting everything from scratch.
Stated that we need a code piece to be cut/pasted in the JavaScript console of web browsers able to store some data (i.e. expiration of followings and permanent followings) we need some considerations.
We can consider expiration of followings as volatile data: something that if lost can be reset to 1 day later from when we loose this data. window.localStorage is a perfect candidate to store these kind of data. If we change web browser the only drawback is that we loose the expiration of followings and we can tolerate to reset them to 1 day later from when we change browser.
While to store the list of permanent followings we need a permanent store even if we change web browser. The best idea that came to my mind is to create an alternative account with which to follow the users we never want to stop following. In my code I used uid 3186068 (a random user), once you have created your own alternative account, just replace the first line of the code block with its uid.
Another thing we need to take care is error handling: API could always have errors. The approach I chosen is to write myFetch which, in case of errors, retries twice the same call; if the error persists, probably we are facing a temporary booyah.live outage. Probably we just need to retry a bit later.
To try to provide a comfortable interface, the code blocks gathers the uid from window.location: to follow the followers of users, just cut/paste the code block on tabs opened on their profiles. For example I run the code from a tab open on https://booyah.live/studio/123456?source=44.
Last, to unfollow users the clean function is called 5 minutes later we paste the code (to not conflict with calls to follow followers) and than is executed one hour later it finishes its job. It is written to access the localStorage in an atomic way, so you can have many of them running simultaneously on different tabs of the same browser, you can not care about it. The only thing you need to take care it that when the window.location changes, all the JavaScript events in the tab are reset; so I suggest to keep a tab open on the home page, paste the code block on it, and forget about this tab; it will be the tab responsible of unfollowing users. Then open other tabs to do what you need, when you hit a user you want to follow the followers, paste the block on it, wait the job is finished and continue to use the tab normally.
// The account we use to store followings
const followingsUID = 3186068;
// Gather the loggedUID from window.localStorage
const { loggedUID } = window.localStorage;
// Gather the CSRF-Token from the cookies
const csrf = document.cookie.split("; ").reduce((ret, _) => (_.startsWith("session_key=") ? _.substr(12) : ret), null);
// APIs could have errors, let's do some retries
async function myFetch(url, options, attempt = 0) {
try {
const res = await fetch("https://booyah.live/api/v3/" + url, options);
const ret = await res.json();
return ret;
} catch(e) {
// After too many consecutive errors, let's abort: we need to retry later
if(attempt === 3) throw e;
return myFetch(url, option, attempt + 1);
}
}
function expire(uid, add = true) {
const { followingsExpire } = window.localStorage;
let expires = {};
try {
// Get and parse followingsExpire from localStorage
expires = JSON.parse(followingsExpire);
} catch(e) {
// In case of error (ex. new browsers) simply init to empty
window.localStorage.followingsExpire = "{}";
}
if(! uid) return expires;
// Set expire after 1 day
if(add) expires[uid] = new Date().getTime() + 3600 * 24 * 1000;
else delete expires[uid];
window.localStorage.followingsExpire = JSON.stringify(expires);
}
async function clean() {
try {
const expires = expire();
const now = new Date().getTime();
for(const uid in expires) {
if(expires[uid] < now) {
await followUser(parseInt(uid), false);
expire(uid, false);
}
}
} catch(e) {}
// Repeat clean in an hour
window.setTimeout(clean, 3600 * 1000);
}
async function fetchFollow(uid, type = "followers", from = 0) {
const { cursor, follower_list, following_list } = await myFetch(`users/${uid}/${type}?cursor=${from}&count=50`);
const got = (type === "followers" ? follower_list : following_list).map(_ => _.uid);
const others = cursor ? await fetchFollow(uid, type, cursor) : [];
return [...got, ...others];
}
async function followUser(uid, follow = true) {
console.log(`${follow ? "F" : "Unf"}ollowing ${uid}...`);
return myFetch(`users/${loggedUID}/followings`, {
method: follow ? "POST" : "DELETE",
headers: { "X-CSRF-Token": csrf },
body: JSON.stringify({ followee_uid: uid, source: 43 })
});
}
async function doAll() {
if(! loggedUID) throw new Error("Can't get 'loggedUID' from localStorage: try to login again");
if(! csrf) throw new Error("Can't get session token from cookies: try to login again");
console.log("Fetching current followings...");
const currentFollowings = await fetchFollow(loggedUID, "followings");
console.log("Fetching permanent followings...");
const permanentFollowings = await fetchFollow(followingsUID, "followings");
console.log("Syncing permanent followings...");
for(const uid of permanentFollowings) {
expire(uid, false);
if(currentFollowings.indexOf(uid) === -1) {
await followUser(uid);
currentFollowings.push(uid);
}
}
// Sync followingsExpire in localStorage
for(const uid of currentFollowings) if(permanentFollowings.indexOf(uid) === -1) expire(uid);
// Call first clean task in 5 minutes
window.setTimeout(clean, 300 * 1000);
// Gather uid from window.location
const match = /\/studio\/(\d+)/.exec(window.location.pathname);
if(match) {
console.log("Fetching this user followers...");
const followings = await fetchFollow(parseInt(match[1]));
for(const uid of followings) {
if(currentFollowings.indexOf(uid) === -1) {
await followUser(uid);
expire(uid);
}
}
}
return "Done";
}
await doAll();
The problem: I strongly suspect a booyah.live API bug
To test my code I run it from https://booyah.live/studio/123456?source=44.
If I run it multiple times I continue to get following output:
Fetching current followings...
Fetching permanent followings...
Syncing permanent followings...
Following 1801775...
Following 143823...
Following 137017...
Fetching this user followers...
Following 16884042...
Following 16166724...
There is bug somewhere! The expected output for subsequent executions in the same tab would be:
Fetching current followings...
Fetching permanent followings...
Syncing permanent followings...
Fetching this user followers...
After seeking the bug in my code without success, I checked booyah.live APIs: if I navigate following URLs (the uids are the ones the code continue to follow in subsequent executions)
https://booyah.live/studio/1801775
https://booyah.live/studio/143823
https://booyah.live/studio/137017
https://booyah.live/studio/16884042
https://booyah.live/studio/16166724
I can clearly see I follow them, but if I navigate https://booyah.live/following (the list of users I follow) I can't find them, neither if I scroll the page till the end.
Since I do exactly the same calls the website does, I strongly suspect the bug is in booyah.live APIs, exactly in the way they handle the cursor parameter.
I suggest you to open a support ticket to booyah.live support team. You could use the test account you provided us: I already provided you the details to do that. ;)

How Can I Query the Parent Record Using Javascript

Click here for picture Overview of the classes/entities
Hi guys, It could be great if someone could re-code and help me on this.I am new in D365 and JS. Basically, how can I query the parent to case_adjustment from adjustment invoice record using JS. I have provide my current code, please help me to review it. I have try everything but so far no luck. Sorry for my unprofessional picture. But I hope you understand it and could help me to code for this situation.
I have try to enable the debugger and it shows that the code cant run the adjustmentTypeLookup. and thats why it cant pass the value to retrieveRecord .Thank you.
function adjustmentInvoiceApproveAmount(executionContext) {
try {
// Get the form context
const formContext = executionContext.getFormContext();
// Extract attribute values from the form
const adjustmentAmount = formContext.getAttribute("case_adjustmentamount").getValue();
const amountDue = formContext.getAttribute("case_amountdue").getValue();
const adjustmentTypeLookup = formContext.getAttribute("case_adjustmenttype").getValue();
// Exit as adjustmenttype is not set
if (!adjustmentTypeLookup) return;
// Extract the adjustment type record ID from the payment type lookup
const adjustmentTypeId = adjustmentTypeLookup[0].id.substring(1, 37);
//console.log("GUID \"case_adjustmenttype\" = " + adjustmentTypeId + " ; " + typeof adjustmentTypeId);
//console.log(adjustmentTypeId);
// Retrieve a SINGLE case_adjustmenttype based on lookup ID on form
Xrm.WebApi.retrieveRecord("case_adjustmenttype", adjustmentTypeId, "$select=case_name").then(
function success(adjustmentType)
{
// If the payment type is credit notes then check payment amount and resit amount
if (adjustmentType.case_name.toLowerCase() == "Credit notes".toLowerCase())
{
if (adjustmentAmount >= amountDue) {
formContext.getEventArgs().preventDefault();
Xrm.Navigation.openErrorDialog({message:"Payment Amount cannot be more than Resit Amount."})
}
}
//Otherwise do nothing
},
function (error)
{
console.log(error.message);
}
);
}
catch (error)
{
console.log(error);
}
}
If you are struggling with the code of retrieving data using web API, I would suggest checking out "CRM REST Builder".
https://github.com/jlattimer/CRMRESTBuilder
Import the solution and refresh the solutions page in dynamics which will show up the button to start this tool. This tool is awesome at generating code for different scenarios.

firebase transaction and if-then-else behaviour

I have some changes in my requirements:
Not only Create/Request/Cancel an entire Offer but do some actions on Offer's details:
Here is an offer in the activeOffers list:
activeOffers
-LKohyZ58cnzn0vCnt9p
details
direction: "city"
seatsCount: 2
timeToGo: 5
uid: "-ABSIFJ0vCnt9p8387a" ---- offering user
A user should be able to 'ask for seats' and if it's successful the Offer record should look like this:
activeOffers
-LKohyZ58cnzn0vCnt9p
details
direction: "city"
seatsCount: 1 ----- reduced count
timeToGo: 5
uid: "-ABSIFJ0vCnt9p8387a"
deals
-GHFFJ0vCnt9p8345b ----- the userId of asking user
seatsCount: 1
status: "asked"
But I have 3 problems after executing the source shown below:
(as shown above offer has 2 seats and a user asks for 1 seat)
After execution in my log I have BOTH "Reducing seats count by 1" and "Not enought seats"... i.e: the 'then' and 'else' part of 'if-then-else' :o
function result is [] - i.e. no deal created.
I'm not sure how to do the TODO: part - to add child (the new deal object) under dealsRef using asking userId as KEY because I think I don't need an autogenerated key here.
input data has the following structure:
data
"uid": "-GHFFJ0vCnt9p8345b", ----- the userId of asking user
"id": "-LKohyZ58cnzn0vCnt9p", ----- the id of offer
"details":
"seatsCount": 1
And here is my code:
dealSeats = function(data) {
const TAG = '[dealSeats]: ';
var details = data.details;
var info = data.info;
var entryRef = db.ref('activeOffers/' + data.id);
var entryDetailsRef = entryRef.child('details');
var seatsCountRef = entryDetailsRef.child('seatsCount');
var success = false;
return seatsCountRef.transaction((current)=>{
var value = current;
if (value >= details.seatsCount) {
success = true;
value = value - details.seatsCount;
console.log(TAG + 'Reducing seats count by ' + details.seatsCount);
} else {
console.log(TAG + 'Not enought seats');
}
return value;
})
.then(()=>{
var deal = [];
if (success) {
console.log(TAG + 'Succes');
deal.seatsCount = details.seatsCount;
deal.status = 'asked';
// TODO: here should add the new deal to dealsRef
return deal;
} else {
console.log(TAG + 'Failure');
return deal;
}
})
}
And as you can see - I'm not sure what is the right way to check if transaction is succeeded...
The reference documentation for DatabaseReference.transaction says:
... until your write succeeds without conflict or you abort the transaction by not returning a value from your update function.
So the way to abort the transaction is by not returning any value from your update function. That means the entire first block can be simplified to:
seatsCountRef.transaction((current)=>{
if (current >= details.seatsCount) {
return value - details.seatsCount;
}
})
Now it either returns the new value, or it returns nothing. The latter will then make Firebase abort the transaction.
To detect the final output of a transaction, I find it easiest to work with a completion callback (instead of a Promise), since it gives you all parameters in one call:
seatsCountRef.transaction((current)=>{
if (current >= details.seatsCount) {
return value - details.seatsCount;
}
}, function(error, committed, snapshot) {
if (error) {
console.log('Transaction failed abnormally!', error);
} else if (!committed) {
console.log('We aborted the transaction, because there are not enough seats.');
} else {
console.log('Seat count updated');
}
})
The most common cause for that first error condition will be that the transaction had to be retried too frequently, meaning that too many users are trying to claim seats at the same time. A typical solution here would be to back off, i.e. have the client retry later.

Categories