Automatically increment order numbers

I’ve been looking for a good example or tutorial, but I’m not able to find one. What I need to do is use either collection-2 or something to automatically create a new order number, incremented from the last order number used.

Starting off with PO123456, when I save this order, the next time I got to make a new PO, it automatically generates the number PO123457.

This can’t be that hard can it?

Could you use Collection.find() with a sort…assuming that you have a timestamp? Maybe something like this pseudocode?

()=>{
   let lastPo =  poCollection.find({},{sort:{createdOn:-1}).fetch()[0]
   return lastPo.number++ ? lastPo : 0
}

This seems like something you could enforce with collection-hooks if you wanted to…so the number is incremented automatically when a new PO document is inserted into the collection.

My concern with this method is dual creation of a PO. Meaning if I got to make a PO and you go to make a PO, we’ll both create the same number +1 from the last PO number. Meaning, we’d both have PO123457. That’s what I’m trying to avoid.

Maybe mongo’s $inc would do it? Supposed to be atomic

Hmm, let me look into that. Have you ever used it before?

I have some time ago when poking around with a tut, not this one but similar. According to the mongo docs it is atomic within a document, so if called from the server it may be ok?

in some business systems… they have a table / collection that contains the last order number that everytime an order is about to be submitted, the system will get a po number from that table / collection, then increment it once the submission is successful

3 Likes

This sounds like it could be a vialbe option. Right now I’m trying to wrap my head around https://github.com/Konecty/meteor-mongo-counter/#api and wishing there was a friggin tutorial with syntax :frowning:

i suggest that the timing of assigning of PO number will be after clicking submit button, because if you rely on auto increment counters you’ll face a problem when the system is being used by multiple user. lets say that user 1 and user 2 is creating a PO at the same time, and since the last PO number in the database is 10… both upon opening their windows. were assigned the PO numbers 11 since logically it is the next number. so when user 1 submits the PO first. it will successfully accept it since it is not yet existing in the database. however by the time the 2nd user submits it will have an error since 11 was already used as a PO number… just my 3 cents :smile:

do this

1 Like

It does feel like something in the database’s wheelhouse…

This is what I’ve done:

Every collection needing an “autoincrement” gets an extra document added: {_id: "autoincrement", value: 0}. I usually put the insert(s) in the server-side Meteor.startup():

Meteor.startup(function() {
  try {
    MyCollection.insert({_id: 'autoincrement', value: 0});
  } catch(err) {
    // Will always get here once the doc's in place, so just ignore
  }
  try {
    AnotherCollection.insert({_id: 'autoincrement', value: 0});
  } catch(err) {
    // Will always get here once the doc's in place, so just ignore
  }
});

Define a helper function to do the autoincrement on a collection (note the node syntax):

const doAutoincrement = function(collection, callback) {
  collection.rawCollection().findAndModify({
    _id: 'autoincrement'
  }, [], {
    $inc: {
      value: 1
    }
  }, {
    'new': true
  }, callback);
}

and define a (synchronous) function to get the next autoincrement value for a collection:

const nextAutoincrement = function(collection) {
  return Meteor.wrapAsync(doAutoincrement)(collection).value;
}

Then, when I want the next autoincrement value:

let nextValue = nextAutoincrement(MyCollection); // 1
nextValue = nextAutoincrement(MyCollection); // 2
nextValue = nextAutoincrement(MyCollection); // 3
let anotherNextValue = nextAutoincrement(AnotherCollection); // 1

Using findAndModify is atomic and is safe even when multiple server instances are being used.

6 Likes

@robfallows, May I know where I should be defining the helper, synchronous functions and where should I be defining the statement to obtain the next autoincremented value.

All of this stuff is in the server-only code, which means that you would normally insert via a Meteor.method. If you need a way of enforcing this via client-side inserts, I suggest using matb33:collection-hooks.

Hi! what is it "rawcollection?"
if i do like
Counters = new Mongo.Collection('counters');
and

          function getNextSequence(order) {
             const ret = Counters.findAndModify(
                    {
                      query: { _id: order },
                      update: { $inc: { seq: 1 } },
                      new: true
                    }
             );
             return ret.seq;
          }

i get
findAndModify is not a function

rawCollection() provides direct access to the underlying MongoDB node library.

That is useful because we get access to the full wealth of methods and options, most of which are not available in Meteor’s minimongo implementation.

Pros:

  • Meteor’s pub/sub can still be used for reactive updates.
  • rawCollection() methods can make use of async and await, meaning sync-style coding can be used.
  • Meteor’s Promise implementation works will with Fibers, so rawCollection() methods can be mixed with minimongo collection methods.

Cons:

  • Only available on the server.

Therefore, your example code may be rewritten as:

async function getNextSequence(order) {
  const ret = await Counters.rawCollection().findAndModify(
    {
      query: { _id: order },
      update: { $inc: { seq: 1 } },
      new: true,
    }
  );
  return ret.seq;
}
1 Like

you mean, that rawCollection() work with external mongo only?

No - I mean you don’t have access to rawCollection() in client code.

Thanks for explanation
:+1:

1 Like

don’t work for me

// collection hook
import Purchases from '../purchases';

Purchases.before.insert(function (userId, doc) {
    doc._id = getNextSequence('purchaseId');
});

import Counters from '../../counters/counters';
function getNextSequence(name) {
    const ret = Counters.rawCollection().findAndModify(
        {
            query: {_id: name},
            update: {$inc: {seq: 1}},
            new: true,
            upsert: true,
        }
    );

    console.log(ret.seq);

    return ret.seq;
}