node.js - 云功能 - 如何编写参考文档并修复 NaN
问题描述
提前感谢您的帮助。
我正在尝试从 firestore 和文档更新时提取价格。然后它需要通过一些 if 语句来确定价格然后创建一个名为付款的新文档,但是付款文档需要引用预订并循环查看是否已经创建了文档,如果有文档,然后它需要更新文档。
数值在数据库中以 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,
});
}
});
});
}
);
});
解决方案
在您当前的代码中,您正在覆盖不应该使用的类型,并且any
不正确地使用禁止的类型。TypeScript 可以为您处理类型,并且 Firebase SDK 设置为支持 TypeScript 开箱即用。
首先,您已将函数配置为响应任何顶级集合(它将响应/users/someId
、/posts/someId
等 - 包括/payments/someId
!):
functions.firestore.document(`{bookings}/{id}`)
应该:
functions.firestore.document(`bookings/{id}`)
接下来,您错误地传递了您的 ID context
:
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
对象不是 a{ data: Record<string, any> }
而是 a DataSnapshot<DocumentData>
(看起来更接近 a { data: () => Record<string, any> }
- 请注意,这是一个方法,而不是属性)。通过覆盖这种类型,通常会抛出错误的 linter 不会更好地了解。
使用您当前的覆盖,TypeScript 不会向您抛出错误,说 that snap.data.isCleaning
is always undefined
、snap.data.isLaundry
is always 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 链之外定义的变量。因为该函数已经拥有当前预订的数据,所以它的 Promise 链中的代码在函数检索数据之前执行cleaningPrice
,discount
以此类推。这意味着您的所有计算最终都会执行类似10 * undefined
which results in之类的操作NaN
。在执行计算之前,您应该确保拥有所需的所有数据。Promise
您可以使用类似的方法来实现这一点Promise.all
,或者切换到async
/await
语法。在此处阅读此内容。
作为一个例子,如何重写你的函数来纠正这些问题(需要错误处理!):
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}`);
});
推荐阅读
- javascript - How can I load a text separately using my modal box?
- excel - Copy from worksheet to worksheet when worksheets names are the same
- elasticsearch - elasticsearch 比索引(索引)更老
- angular - 蓝牙串行写入在 Ionic - v4 中不起作用
- php - 从 PHP 获取 MySQL 字符串时不相等的二进制字符比较
- c++ - 如何解决 *** `./key_server' 中的错误:free():invalid pointer: 0x00007f361a97b6e8 in c++
- c# - C# IntelliSense 在 Visual Studio Community 2017 和 Unity 中不起作用
- teradata - tdstats.UDFCONCAT 参数 varchar 限制
- android - No solution works for handling of NetworkOnMainThreadException
- vb.net - how can i create dynamically flowlayoutpanels and tabpages in my form