Рассчитать общую сумму элементов транзакции и позиции транзакции с помощью запроса mongoose - PullRequest
0 голосов
/ 28 сентября 2019

В мангусте у меня есть коллекция для транзакций.У каждой транзакции есть список элементов. Пример примерно так:

var transactionItemSchema = new mongoose.Schema({
    productId: String,
    quantity: Number,
    price: Number
});

var transactionSchema = new mongoose.Schema({
    details: String,
    items: [transactionItemSchema ],
}, {
    timestamps: true
});

Мне нужно рассчитать общую стоимость каждой позиции, умножив цену * количество и округлив 2 десятичных знака, но также мне нужно получить общую сумму транзакции, суммируя всеитого в транзакции.Так, например, если у меня есть эти транзакции в монго:

[{
  details: 'First Transaction',
  items: [{
      price: 5.2,
      quantity: 2
    }, {
      price: 4,
      quantity: 3
    }]
  }, {
  details: 'First Transaction',
  items: [{
      price: 0.333,
      quantity: 3
    }]
  }]

возвращает что-то подобное при получении транзакций:

[{
  total: 22.40,
  details: 'First Transaction',
  items: [{
    price: 5.2,
    quantity: 2,
    total: 10.40
  }, {
    price: 4,
    quantity: 3,
    total: 12.00
  }]
}, {
  total: 1.00,
  details: 'Second Transaction',
  items: [{
    price: 0.333,
    quantity: 3,
    total: 1.00
  }]
}]

Есть ли способ, которым мы можем достичь этого с некоторыми агрегациями с помощью mongoose

1 Ответ

1 голос
/ 28 сентября 2019

Вы хотите $map и $multiply здесь.

Предполагая, что модель вызывает Transaction:

Transaction.aggregate([
  { "$addFields": {
    "items": {
      "$map": {
        "input": "$items",
        "in": {
          "$mergeObjects": [
            "$$this",
            { "total": { "$round": [{ "$multiply": [ "$$this.price", "$$this.quantity" ] }, 2] } }
          ]
        }
      }
    }
  }}
])

Или без $mergeObjects:

Transaction.aggregate([
  { "$addFields": {
    "total": {
      "$sum": {
        "$map": {
          "input": "$items",
          "in": {
            "$round": [{ "$multiply": [ "$$this.price", "$$this.quantity" ] }, 2]
          }
        }
      }
    },
    "items": {
      "$map": {
        "input": "$items",
        "in": {
          "price": "$$this.price",
          "quantity": "$$this.quantity",
          "total": { "$round": [{ "$multiply": [ "$$this.price", "$$this.quantity" ] }, 2] }
        }
      }
    }
  }}
])

Оператор $map в основном используется для преобразований массива , в которых вы предоставляетемассив input и выражение для применения к каждому элементу массива, которое определяет выходные данные объекта для каждого элемента.Здесь $multiply применяется с двумя аргументами для «умножения» результата.

$mergeObjects равно , необязательно , используемое какспособ взять существующие свойства объекта для каждого элемента (price и quantity) и включить их в выходной объект.Альтернатива состоит в том, чтобы вручную указывать свойства в выходных объектах для каждого элемента, как показано.

Конечно, для всего документа то же самое, по сути, предоставляется, но просто возвращает одно значениеи передаем это оператору $sum для "суммирования" результатов

Все это говорит о том, что нет ничего плохого в простой манипуляции с результатом post returnс сервера:

let results = await Transaction.find().lean();

// Then manipulate the items arrays

results = results.map(r =>
  ({
    ...r,
    total: r.items.reduce((o, i) =>
       o + parseFloat((i.price * i.quantity).toFixed(2)), 0),
    items: r.items.map(i =>
      ({ ...i, total: parseFloat((i.price * i.quantity).toFixed(2)) })
    )
  })
);

Просто обратите внимание на использование lean() здесь, которое возвращает простые объекты JavaScript, а не Mongoose Documents и, таким образом, позволяет манипулировать структурой возвращаемых результатов.


Вот полный список обоих подходов:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/test';
const opts = { useNewUrlParser: true, useUnifiedTopology: true };

mongoose.Promise = global.Promise;

mongoose.set('debug', true);
mongoose.set('useCreateIndex', true);
mongoose.set('useFindAndModify', false);

const transactionItemSchema = new Schema({
  productId: String,
  quantity: Number,
  price: Number
});

const transactionSchema = new Schema({
  details: String,
  items: [transactionItemSchema]
},{
  timestamps: true
});

const Transaction = mongoose.model('Transaction', transactionSchema);


const initialData = [
  {
    details: 'First Transaction',
    items: [
      { price: 5.2, quantity: 2 },
      { price: 4, quantity: 3 }
    ]
  },
  {
    details: 'Second Transaction',
    items: [
      { price: 0.333, quantity: 3 }
    ]
  }
];

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {
    const conn = await mongoose.connect(uri, opts);

    // Clean data
    await Promise.all(
      Object.values(conn.models).map(m => m.deleteMany())
    );

    await Transaction.insertMany(initialData);

    // Aggregate example

    let result1 = await Transaction.aggregate([
      { "$addFields": {
        "total": {
          "$sum": {
            "$map": {
              "input": "$items",
              "in": {
                "$round": [
                  { "$multiply": [ "$$this.price", "$$this.quantity" ] },
                  2
                ]
              }
            }
          }
        },
        "items": {
          "$map": {
            "input": "$items",
            "in": {
              "$mergeObjects": [
                "$$this",
                { "total": {
                  "$round": [
                    { "$multiply": [ "$$this.price", "$$this.quantity" ] },
                    2
                  ]
                }}
              ]
            }
          }
        }
      }}
    ]);

    log({ result1 });


    // Plain JavaScript example

    let result2 = await Transaction.find().lean();

    result2 = result2.map(r =>
      ({
        ...r,
        total: r.items.reduce((o, i) =>
           o + parseFloat((i.price * i.quantity).toFixed(2)), 0),
        items: r.items.map(i =>
          ({ ...i, total: parseFloat((i.price * i.quantity).toFixed(2)) })
        )
      })
    );

    log({ result2 });

  } catch (e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }


})();

И вывод:

Mongoose: transactions.deleteMany({}, {})
Mongoose: transactions.insertMany([ { _id: 5d8f4dfcaf9f6a2f8ec28039, details: 'First Transaction', items: [ { _id: 5d8f4dfcaf9f6a2f8ec2803b, price: 5.2, quantity: 2 }, { _id: 5d8f4dfcaf9f6a2f8ec2803a, price: 4, quantity: 3 } ], __v: 0, createdAt: 2019-09-28T12:11:40.060Z, updatedAt: 2019-09-28T12:11:40.061Z }, { _id: 5d8f4dfcaf9f6a2f8ec2803c, details: 'Second Transaction', items: [ { _id: 5d8f4dfcaf9f6a2f8ec2803d, price: 0.333, quantity: 3 } ], __v: 0, createdAt: 2019-09-28T12:11:40.062Z, updatedAt: 2019-09-28T12:11:40.062Z } ], {})
Mongoose: transactions.aggregate([ { '$addFields': { total: { '$sum': { '$map': { input: '$items', in: { '$round': [ { '$multiply': [ '$$this.price', '$$this.quantity' ] }, 2 ] } } } }, items: { '$map': { input: '$items', in: { '$mergeObjects': [ '$$this', { total: { '$round': [ { '$multiply': [Array] }, 2 ] } } ] } } } } } ], {})
{
  "result1": [
    {
      "_id": "5d8f4dfcaf9f6a2f8ec28039",
      "details": "First Transaction",
      "items": [
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803b",
          "price": 5.2,
          "quantity": 2,
          "total": 10.4
        },
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803a",
          "price": 4,
          "quantity": 3,
          "total": 12
        }
      ],
      "__v": 0,
      "createdAt": "2019-09-28T12:11:40.060Z",
      "updatedAt": "2019-09-28T12:11:40.061Z",
      "total": 22.4
    },
    {
      "_id": "5d8f4dfcaf9f6a2f8ec2803c",
      "details": "Second Transaction",
      "items": [
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803d",
          "price": 0.333,
          "quantity": 3,
          "total": 1
        }
      ],
      "__v": 0,
      "createdAt": "2019-09-28T12:11:40.062Z",
      "updatedAt": "2019-09-28T12:11:40.062Z",
      "total": 1
    }
  ]
}
Mongoose: transactions.find({}, { projection: {} })
{
  "result2": [
    {
      "_id": "5d8f4dfcaf9f6a2f8ec28039",
      "details": "First Transaction",
      "items": [
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803b",
          "price": 5.2,
          "quantity": 2,
          "total": 10.4
        },
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803a",
          "price": 4,
          "quantity": 3,
          "total": 12
        }
      ],
      "__v": 0,
      "createdAt": "2019-09-28T12:11:40.060Z",
      "updatedAt": "2019-09-28T12:11:40.061Z",
      "total": 22.4
    },
    {
      "_id": "5d8f4dfcaf9f6a2f8ec2803c",
      "details": "Second Transaction",
      "items": [
        {
          "_id": "5d8f4dfcaf9f6a2f8ec2803d",
          "price": 0.333,
          "quantity": 3,
          "total": 1
        }
      ],
      "__v": 0,
      "createdAt": "2019-09-28T12:11:40.062Z",
      "updatedAt": "2019-09-28T12:11:40.062Z",
      "total": 1
    }
  ]
}
...