CLOUD FUNCTION - 如何编写参考文档并修复 NaN
CLOUD FUNCTION - How to write a reference document and fix NaN
提前感谢您的帮助。
我正在尝试从 firestore 中提取价格以及更新文档时的价格。然后它需要通过一些 if 语句来确定价格,然后创建一个名为 payments 的新文件,但是 payments 文件需要引用预订并循环查看是否已经创建了一个文件,如果有一个文件,然后它需要更新文档。
数据库中的数值为 NaN,我不明白为什么。
请看下面的代码:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.paymentCalcAmount = functions.firestore
.document(`{bookings}/{id}`)
.onUpdate((snapshots, context) => {
const payments = admin.firestore().collection("payments");
const collection = admin.firestore().collection("bookings");
const id = context.params.id;
let startDate;
let endDate;
let cabinetVal = 0;
let laundry = 0;
let fridge = 0;
let oven = 0;
let window = 0;
let wall = 0;
let cleaningPrice: number;
let discount: number;
let stay_Over: number;
let visiting: number;
let houspitality_cut: number = 0;
let service_provider_cut: number = 0;
let TOTALHRS: number = 0;
let TOTALPRICE: number = 0;
let discountedPrice: number = 0;
db.collection("prices")
.doc("price")
.get()
.then(
(snapshot: {
data: {
cleaning: number;
discount: number;
stay_over: number;
visiting: number;
};
}) => {
cleaningPrice = snapshot.data.cleaning;
discount = 100 - snapshot.data.discount;
stay_Over = snapshot.data.stay_over;
visiting = snapshot.data.visiting;
}
);
db.collection('bookings')
.doc('{id}')
.get()
.then(
(snap: {
data: {
isCleaning: boolean;
isLaundry: boolean;
isFridge: boolean;
isOven: boolean;
isCabinet: boolean;
isWindow: boolean;
isWall: boolean;
stayOver: boolean;
startDate: any;
endDate: any;
};
}) => {
if (snap.data.isCleaning === true) {
if (snap.data.isCabinet === true) {
cabinetVal = 1;
}
if (snap.data.isLaundry === true) {
laundry = 1.5;
}
if (snap.data.isFridge === true) {
fridge = 0.5;
}
if (snap.data.isOven === true) {
oven = 0.5;
}
if (snap.data.isWindow === true) {
window = 1;
}
if (snap.data.isWall === true) {
wall = 1;
}
TOTALHRS = cabinetVal + laundry + fridge + oven + window + wall;
TOTALPRICE = TOTALHRS * cleaningPrice;
houspitality_cut = (TOTALPRICE / 100) * 27;
service_provider_cut = TOTALPRICE - houspitality_cut;
if (discount === 100) {
discountedPrice = (TOTALPRICE / 100) * discount;
TOTALPRICE = discountedPrice;
houspitality_cut = (TOTALPRICE / 100) * 27;
service_provider_cut = TOTALPRICE - houspitality_cut;
}
} else {
if (snap.data.stayOver === true) {
startDate = snap.data.startDate;
endDate = snap.data.endDate;
const days = Math.round((startDate-endDate)/(1000*60*60*24));
TOTALPRICE = stay_Over * days;
houspitality_cut = (TOTALPRICE / 100) * 27;
service_provider_cut = TOTALPRICE - houspitality_cut;
} else {
startDate = snap.data.startDate;
endDate = snap.data.endDate;
const difference_in_time = endDate - startDate;
const difference_in_days = difference_in_time / (1000 * 3600 * 24);
TOTALPRICE = visiting * difference_in_days;
houspitality_cut = (TOTALPRICE / 100) * 27;
service_provider_cut = TOTALPRICE - houspitality_cut;
}
}
db.collection("payments")
.doc()
.get()
.then((Snapshot: { docs: any[] }) => {
Snapshot.docs.forEach((docs) => {
if (
docs.data.booking_ref === `${collection}${id}`
) {
return payments.update({
payment_total: TOTALPRICE,
houspitality_cut: houspitality_cut,
service_provider_cut: service_provider_cut,
total_hours: TOTALHRS,
});
} else {
return payments.add({
booking_ref: collection,id,
payment_total: TOTALPRICE,
houspitality_cut: houspitality_cut,
service_provider_cut: service_provider_cut,
total_hours: TOTALHRS,
});
}
});
});
}
);
});
如果这些值被错误地存储为字符串(注意值周围的双引号),您首先必须解析为数字,以便计算它们。我的意思是,您希望计算结果为:1 + 1 = 2
,但实际情况可能是:"1" + "1" = "11"
(结果为 NaN
)。
在您当前的代码中,您正在覆盖不应该使用的类型,并且不正确地使用 any
等禁用类型。 TypeScript 可以为您处理类型,并且 Firebase SDK 设置为开箱即用地支持 TypeScript。
开始时,您已将函数配置为响应任何顶级集合(它将响应 /users/someId
、/posts/someId
等 - 包括 /payments/someId
!):
functions.firestore.document(`{bookings}/{id}`)
应该是:
functions.firestore.document(`bookings/{id}`)
接下来,您错误地传入了 context
的 ID:
db.collection('bookings').doc('{id}')
应该是以下之一:
db.collection('bookings').doc(id)
db.collection('bookings').doc(context.params.id)
snapshots.after.ref
因为这个Cloud Function传入了与这个事件相关的数据,所以你不需要再次获取它的数据:
const bookingSnapshot = await db.collection('bookings').doc(id).get()
可以换成
const bookingSnapshot = snapshots.after;
关于覆盖类型,这些行:
docRef
.get()
.then(
(snap: {
data: {
isCleaning: boolean;
isLaundry: boolean;
isFridge: boolean;
isOven: boolean;
isCabinet: boolean;
isWindow: boolean;
isWall: boolean;
stayOver: boolean;
startDate: any;
endDate: any;
};
}) => {
应该是:
docRef
.get()
.then((snap) => { // snap is already a DataSnapshot<DocumentData>
snap
对象不是 { data: Record<string, any> }
而是 DataSnapshot<DocumentData>
(看起来更接近 { data: () => Record<string, any> }
- 请注意这是一个方法,而不是 属性).通过覆盖此类型,通常会抛出错误的 linter 并不知道更好。
使用您当前的覆盖,TypeScript 不会向您抛出错误说 snap.data.isCleaning
总是 undefined
,snap.data.isLaundry
是 总是 undefined
,等等。
如果要在此引用处定义数据的形状,请使用:
docRef
.withConverter(myDataConverter) // see https://googleapis.dev/nodejs/firestore/latest/global.html#FirestoreDataConverter
.get()
.then((snap) => {
或
docRef
.get()
.then((snap: admin.firestore.DataSnapshot<{
isCleaning: boolean;
isLaundry: boolean;
isFridge: boolean;
isOven: boolean;
isCabinet: boolean;
isWindow: boolean;
isWall: boolean;
stayOver: boolean;
startDate?: number; // <-- don't use any here
endDate?: number; // <-- don't use any here
}>) => {
因为您还覆盖了集合查询的类型,所以您也不会收到以下行的错误:
db.collection("payments")
.doc()
.get()
.then((snapshot: { docs: any[] }) => {
应该是(获取 /payments
集合中的所有文档):
db.collection("payments") // note: doc() was removed
.get()
.then((querySnapshot) => { // querySnapshot is already a QuerySnapshot<DocumentData>
最后,您正在使用在 Promise 链之外定义的变量。因为函数已经有当前预订的数据,所以在函数检索 cleaningPrice
、discount
等数据之前执行其 Promise 链中的代码。这意味着你所有的计算最终都会做类似 10 * undefined
的事情,结果是 NaN
。在执行计算之前,您应该确保拥有所需的所有数据。您可以使用 Promise
methods like Promise.all
, or switch to async
/await
syntax. Read up on this here.
实现此目的
作为一个例子如何重写你的函数来纠正这些问题(需要错误处理!):
exports.paymentCalcAmount = functions.firestore
.document(`bookings/{id}`)
.onUpdate(async (change, context) => {
const db = admin.firestore();
const bookingId = context.params.id;
const bookingData = change.after.data() as {
isCleaning: boolean;
isLaundry: boolean;
isFridge: boolean;
isOven: boolean;
isCabinet: boolean;
isWindow: boolean;
isWall: boolean;
stayOver: boolean;
startDate?: number;
endDate?: number;
};
const bookingPaymentRef = db
.collection("payments")
.doc(bookingId);
const pricingData = await db
.collection("prices")
.doc("price")
.get()
.then(snap => snap.data());
if (bookingData.isCleaning) {
const cleaningHourlyRate = pricingData.cleaning;
const cleaningRateMultiplier = (100 - (pricingData.discount || 0)) / 100;
let total_hours = 0;
if (bookingData.isCabinet) total_hours += 1;
if (bookingData.isLaundry) total_hours += 1.5;
if (bookingData.isFridge) total_hours += 0.5;
if (bookingData.isOven) total_hours += 0.5;
if (bookingData.isWindow) total_hours += 1;
if (bookingData.isWall) total_hours += 1;
const payment_total = cleaningHourlyRate * total_hours * cleaningRateMultiplier;
const houspitality_cut = payment_total * 0.27;
const service_provider_cut = payment_total - houspitality_cut;
await bookingPaymentRef
.set({
payment_total,
houspitality_cut,
service_provider_cut,
total_hours
}, { merge: true });
} else {
const stayOverDailyRate = pricingData.stayOver;
const visitingDailyRate = pricingData.visiting;
const deltaTime = bookingData.endDate - bookingData.startDate;
const deltaTimeInDays = deltaTime/(1000*60*60*24);
const payment_total = bookingData.stayOver
? stayOverDailyRate * Math.round(deltaTimeInDays)
: visitingDailyRate * deltaTimeInDays;
const houspitality_cut = payment_total * 0.27;
const service_provider_cut = payment_total - houspitality_cut;
await bookingPaymentRef
.set({
payment_total,
houspitality_cut,
service_provider_cut,
total_hours: 0,
}, { merge: true });
}
console.log(`Successfully updated booking #${id}`);
});
提前感谢您的帮助。
我正在尝试从 firestore 中提取价格以及更新文档时的价格。然后它需要通过一些 if 语句来确定价格,然后创建一个名为 payments 的新文件,但是 payments 文件需要引用预订并循环查看是否已经创建了一个文件,如果有一个文件,然后它需要更新文档。
数据库中的数值为 NaN,我不明白为什么。
请看下面的代码:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.paymentCalcAmount = functions.firestore
.document(`{bookings}/{id}`)
.onUpdate((snapshots, context) => {
const payments = admin.firestore().collection("payments");
const collection = admin.firestore().collection("bookings");
const id = context.params.id;
let startDate;
let endDate;
let cabinetVal = 0;
let laundry = 0;
let fridge = 0;
let oven = 0;
let window = 0;
let wall = 0;
let cleaningPrice: number;
let discount: number;
let stay_Over: number;
let visiting: number;
let houspitality_cut: number = 0;
let service_provider_cut: number = 0;
let TOTALHRS: number = 0;
let TOTALPRICE: number = 0;
let discountedPrice: number = 0;
db.collection("prices")
.doc("price")
.get()
.then(
(snapshot: {
data: {
cleaning: number;
discount: number;
stay_over: number;
visiting: number;
};
}) => {
cleaningPrice = snapshot.data.cleaning;
discount = 100 - snapshot.data.discount;
stay_Over = snapshot.data.stay_over;
visiting = snapshot.data.visiting;
}
);
db.collection('bookings')
.doc('{id}')
.get()
.then(
(snap: {
data: {
isCleaning: boolean;
isLaundry: boolean;
isFridge: boolean;
isOven: boolean;
isCabinet: boolean;
isWindow: boolean;
isWall: boolean;
stayOver: boolean;
startDate: any;
endDate: any;
};
}) => {
if (snap.data.isCleaning === true) {
if (snap.data.isCabinet === true) {
cabinetVal = 1;
}
if (snap.data.isLaundry === true) {
laundry = 1.5;
}
if (snap.data.isFridge === true) {
fridge = 0.5;
}
if (snap.data.isOven === true) {
oven = 0.5;
}
if (snap.data.isWindow === true) {
window = 1;
}
if (snap.data.isWall === true) {
wall = 1;
}
TOTALHRS = cabinetVal + laundry + fridge + oven + window + wall;
TOTALPRICE = TOTALHRS * cleaningPrice;
houspitality_cut = (TOTALPRICE / 100) * 27;
service_provider_cut = TOTALPRICE - houspitality_cut;
if (discount === 100) {
discountedPrice = (TOTALPRICE / 100) * discount;
TOTALPRICE = discountedPrice;
houspitality_cut = (TOTALPRICE / 100) * 27;
service_provider_cut = TOTALPRICE - houspitality_cut;
}
} else {
if (snap.data.stayOver === true) {
startDate = snap.data.startDate;
endDate = snap.data.endDate;
const days = Math.round((startDate-endDate)/(1000*60*60*24));
TOTALPRICE = stay_Over * days;
houspitality_cut = (TOTALPRICE / 100) * 27;
service_provider_cut = TOTALPRICE - houspitality_cut;
} else {
startDate = snap.data.startDate;
endDate = snap.data.endDate;
const difference_in_time = endDate - startDate;
const difference_in_days = difference_in_time / (1000 * 3600 * 24);
TOTALPRICE = visiting * difference_in_days;
houspitality_cut = (TOTALPRICE / 100) * 27;
service_provider_cut = TOTALPRICE - houspitality_cut;
}
}
db.collection("payments")
.doc()
.get()
.then((Snapshot: { docs: any[] }) => {
Snapshot.docs.forEach((docs) => {
if (
docs.data.booking_ref === `${collection}${id}`
) {
return payments.update({
payment_total: TOTALPRICE,
houspitality_cut: houspitality_cut,
service_provider_cut: service_provider_cut,
total_hours: TOTALHRS,
});
} else {
return payments.add({
booking_ref: collection,id,
payment_total: TOTALPRICE,
houspitality_cut: houspitality_cut,
service_provider_cut: service_provider_cut,
total_hours: TOTALHRS,
});
}
});
});
}
);
});
如果这些值被错误地存储为字符串(注意值周围的双引号),您首先必须解析为数字,以便计算它们。我的意思是,您希望计算结果为:1 + 1 = 2
,但实际情况可能是:"1" + "1" = "11"
(结果为 NaN
)。
在您当前的代码中,您正在覆盖不应该使用的类型,并且不正确地使用 any
等禁用类型。 TypeScript 可以为您处理类型,并且 Firebase SDK 设置为开箱即用地支持 TypeScript。
开始时,您已将函数配置为响应任何顶级集合(它将响应 /users/someId
、/posts/someId
等 - 包括 /payments/someId
!):
functions.firestore.document(`{bookings}/{id}`)
应该是:
functions.firestore.document(`bookings/{id}`)
接下来,您错误地传入了 context
的 ID:
db.collection('bookings').doc('{id}')
应该是以下之一:
db.collection('bookings').doc(id)
db.collection('bookings').doc(context.params.id)
snapshots.after.ref
因为这个Cloud Function传入了与这个事件相关的数据,所以你不需要再次获取它的数据:
const bookingSnapshot = await db.collection('bookings').doc(id).get()
可以换成
const bookingSnapshot = snapshots.after;
关于覆盖类型,这些行:
docRef
.get()
.then(
(snap: {
data: {
isCleaning: boolean;
isLaundry: boolean;
isFridge: boolean;
isOven: boolean;
isCabinet: boolean;
isWindow: boolean;
isWall: boolean;
stayOver: boolean;
startDate: any;
endDate: any;
};
}) => {
应该是:
docRef
.get()
.then((snap) => { // snap is already a DataSnapshot<DocumentData>
snap
对象不是 { data: Record<string, any> }
而是 DataSnapshot<DocumentData>
(看起来更接近 { data: () => Record<string, any> }
- 请注意这是一个方法,而不是 属性).通过覆盖此类型,通常会抛出错误的 linter 并不知道更好。
使用您当前的覆盖,TypeScript 不会向您抛出错误说 snap.data.isCleaning
总是 undefined
,snap.data.isLaundry
是 总是 undefined
,等等。
如果要在此引用处定义数据的形状,请使用:
docRef
.withConverter(myDataConverter) // see https://googleapis.dev/nodejs/firestore/latest/global.html#FirestoreDataConverter
.get()
.then((snap) => {
或
docRef
.get()
.then((snap: admin.firestore.DataSnapshot<{
isCleaning: boolean;
isLaundry: boolean;
isFridge: boolean;
isOven: boolean;
isCabinet: boolean;
isWindow: boolean;
isWall: boolean;
stayOver: boolean;
startDate?: number; // <-- don't use any here
endDate?: number; // <-- don't use any here
}>) => {
因为您还覆盖了集合查询的类型,所以您也不会收到以下行的错误:
db.collection("payments")
.doc()
.get()
.then((snapshot: { docs: any[] }) => {
应该是(获取 /payments
集合中的所有文档):
db.collection("payments") // note: doc() was removed
.get()
.then((querySnapshot) => { // querySnapshot is already a QuerySnapshot<DocumentData>
最后,您正在使用在 Promise 链之外定义的变量。因为函数已经有当前预订的数据,所以在函数检索 cleaningPrice
、discount
等数据之前执行其 Promise 链中的代码。这意味着你所有的计算最终都会做类似 10 * undefined
的事情,结果是 NaN
。在执行计算之前,您应该确保拥有所需的所有数据。您可以使用 Promise
methods like Promise.all
, or switch to async
/await
syntax. Read up on this here.
作为一个例子如何重写你的函数来纠正这些问题(需要错误处理!):
exports.paymentCalcAmount = functions.firestore
.document(`bookings/{id}`)
.onUpdate(async (change, context) => {
const db = admin.firestore();
const bookingId = context.params.id;
const bookingData = change.after.data() as {
isCleaning: boolean;
isLaundry: boolean;
isFridge: boolean;
isOven: boolean;
isCabinet: boolean;
isWindow: boolean;
isWall: boolean;
stayOver: boolean;
startDate?: number;
endDate?: number;
};
const bookingPaymentRef = db
.collection("payments")
.doc(bookingId);
const pricingData = await db
.collection("prices")
.doc("price")
.get()
.then(snap => snap.data());
if (bookingData.isCleaning) {
const cleaningHourlyRate = pricingData.cleaning;
const cleaningRateMultiplier = (100 - (pricingData.discount || 0)) / 100;
let total_hours = 0;
if (bookingData.isCabinet) total_hours += 1;
if (bookingData.isLaundry) total_hours += 1.5;
if (bookingData.isFridge) total_hours += 0.5;
if (bookingData.isOven) total_hours += 0.5;
if (bookingData.isWindow) total_hours += 1;
if (bookingData.isWall) total_hours += 1;
const payment_total = cleaningHourlyRate * total_hours * cleaningRateMultiplier;
const houspitality_cut = payment_total * 0.27;
const service_provider_cut = payment_total - houspitality_cut;
await bookingPaymentRef
.set({
payment_total,
houspitality_cut,
service_provider_cut,
total_hours
}, { merge: true });
} else {
const stayOverDailyRate = pricingData.stayOver;
const visitingDailyRate = pricingData.visiting;
const deltaTime = bookingData.endDate - bookingData.startDate;
const deltaTimeInDays = deltaTime/(1000*60*60*24);
const payment_total = bookingData.stayOver
? stayOverDailyRate * Math.round(deltaTimeInDays)
: visitingDailyRate * deltaTimeInDays;
const houspitality_cut = payment_total * 0.27;
const service_provider_cut = payment_total - houspitality_cut;
await bookingPaymentRef
.set({
payment_total,
houspitality_cut,
service_provider_cut,
total_hours: 0,
}, { merge: true });
}
console.log(`Successfully updated booking #${id}`);
});