首页 > 解决方案 > 云功能 - 如何编写参考文档并修复 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,
                  });
                }
              });
            });
        }
      );
  });

标签: node.jsfirebasegoogle-cloud-functions

解决方案


在您当前的代码中,您正在覆盖不应该使用的类型,并且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.isCleaningis always undefinedsnap.data.isLaundryis 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 链中的代码在函数检索数据之前执行cleaningPricediscount以此类推。这意味着您的所有计算最终都会执行类似10 * undefinedwhich 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}`);
  });

推荐阅读