This is related to a question I asked previously:
How to get customer aging fields from a Netsuite restlet
The technique described in the answer to that question works great, but it doesn't handle credit memos. I'm having problems figuring out how to take credit memos into account.
For example, I have a customer record that displays the following values:
BALANCE
1950.00
OVERDUE BALANCE
2000.00
CURRENT 1-30 DAYS 31-60 DAYS 61-90 DAYS OVER 90 DAYS
0.00 -50.00 2,000.00 0.00 0.00
I can pull the 2000 out just fine, but I can't seem to get the -50 from the credit memo.
I tried adjusting the invoice query to do this:
new nlobjSearchFilter('amountremaining', null, 'notequalto', 0),
I also tried doing a separate query for credit memos:
var agingcmemo = nlapiSearchRecord(
'creditmemo',
null,
[
new nlobjSearchFilter('daysoverdue', null, 'greaterthan', 0),
new nlobjSearchFilter('mainline', null, 'is', 'T'),
new nlobjSearchFilter('amountremaining', null, 'notequalto', 0),
new nlobjSearchFilter('entity', null, 'is', result[0].id)
],
[
new nlobjSearchColumn('entity', null, 'group'),
new nlobjSearchColumn('formulanumeric', null, 'sum').setFormula('case when {daysoverdue} < 31 then {amountremaining} else 0 end'),
new nlobjSearchColumn('formulanumeric', null, 'sum').setFormula('case when {daysoverdue} between 31 and 60 then {amountremaining} else 0 end'),
new nlobjSearchColumn('formulanumeric', null, 'sum').setFormula('case when {daysoverdue} between 61 and 90 then {amountremaining} else 0 end'),
new nlobjSearchColumn('formulanumeric', null, 'sum').setFormula('case when {daysoverdue} > 90 then {amountremaining} else 0 end')
]
);
That query returns no rows. If I remove all conditions except for the entity it returns two rows, neither of which is the actual credit memo.
Anyone have any ideas on how to get those right numbers including the credit memos?
there's no concept of daysoverdue with a credit memo. If you run the code below does that sum to $50? You should be able to use that to get your open credit memo value and combine that with your open invoice amounts.
The other than that is it possible that you have an unapplied payment or customer deposit?
var agingcmemo = nlapiSearchRecord(
'creditmemo',
null,
[
new nlobjSearchFilter('mainline', null, 'is', 'T'),
new nlobjSearchFilter('status', null, 'anyof', ['CustCred:A']),
new nlobjSearchFilter('entity', null, 'is', 996)
],
[
new nlobjSearchColumn('entity', null, 'group'),
new nlobjSearchColumn('amountremaining', null, 'sum')
]
);
agingcmemo.forEach(function(c){
var cols = c.getAllColumns();
cols.forEach(function(col, idx){ console.log(idx +': '+c.getValue(col));});
console.log('');
});
I've used a different approach to building an aging report in a Saved Search that might work better for you as it takes credit memos into account as well. (The groups below age by months rather than 30 day increments but you can replace them with the formula you were using before):
You can define "Transaction Type" in the search criteria, this way you can create saved searches for each specific transaction types. Alternately you can add transaction type as a filter even color code it by the transaction types. In my opinion transaction search is the mother of all searches in NS because majority of objects and fields are exposed to this search type.
Related
I'm having some issues designing a query that deals with overlapping dates.
So here's the scenario, I can't reveal too much about the actual project but here is a similar example. Lets say I have a FleaMarket. It has a bunch of data about itself such as name, location, etc.
So a FleaMarket would have many Stalls, that are available to be booked for a portion of the year (as short as 2 days, as long as all year sort of thing). So the FleaMarket needs to specify when in a year it will be open. Most scenarios would either be open all year, or all summer/fall, but it could possible be broken down further (because seasons determine pricing). Each FleaMarket would define their Seasons which would include a startDate and endDate (including year).
Here's an ERD to model this example:
When a user attempts to book a Stall, they have already selected a FleaMarket (although ideally it would be nice to search based on availability in the future). It's really easy to tell if a Stall is already booked for the requested dates:
bookings = await Booking.find({
startDate: { $lt: <requested end date> },
endDate: { $gt: <requested start date> },
fleaMarketId: <flea market id>,
}).select('stallId');
bookedIds = bookings.map(b => b.stallId);
stalls = await Stall.find({
fleaMarketId: <flea marked id>,
_id: { $nin: bookedIds }
});
The issue I'm having is determining if a Stall is available for the specified Season. The problem comes that 2 seasons could be sequential, so you could make a booking that spans 2 seasons.
I originally tried a query like so:
seasons = await Season.find({
fleaMarketId: <flea market id>,
startDate: { $lt: <requested end date> },
endDate: {$gt: <requested start date> }
});
And then programatically checked if any returned seasons were sequential, and plucked the available stalls from that that existed in all seasons. But unfortunately I just realized this won't work if the requested date only partially overlaps with a season (ex: requested Jan 1 2020 - Jan 10 2020, but the season is defined as Jan 2 2020 - May 1 2020)
Is there a way I can handle checking for completely overlapping dates that could possible overlap with multiple documents? I was thinking about calculating and storing the current and future available season dates (stored as total ranges) denormalized on the Stall.
At this point I'm almost thinking I need to restructure the schema quite a bit. Any recommendations? I know this seems very relational, but pretty much everywhere else in the application doesn't really do much with the relationships. It's just this search that is quite problematic.
Update:
I just had the thought of maybe creating some sort of Calendar Document that can store a centralized list of availability for a FleaMarket, that would do a rolling update to only store future and present data, and slowly wiping away historical data, or maybe archiving it in a different format. Perhaps this will solve my issue, I will be discussing it with my team soon.
So as I said in an update in my post, I came up with the idea to create a rolling calendar.
For anyone who is interested, here's what I got:
I created an Availability collection, that contains documents like the following:
{
marketId: ObjectId('5dd705c0eeeaf900450e7009'),
stallId: ObjectId('5dde9fc3bf30e500280f80ce'),
availableDates: [
{
date: '2020-01-01T00:00:00.000Z',
price: 30.0,
seasonId: '5dd708e7534f3700a9cad0e7',
},
{
date: '2020-01-02T00:00:00.000Z',
price: 30.0,
seasonId: '5dd708e7534f3700a9cad0e7',
},
{
date: '2020-01-03T00:00:00.000Z',
price: 35.0,
seasonId: '5dd708e7534f3700a9cad0e8',
}
],
bookedDuring: [
'2020-01-01T00:00:00.000Z'
'2020-01-02T00:00:00.000Z'
]
}
Then handling updates to this collection:
Seasons
when creating, $push new dates onto each stall (and delete dates from the past)
When updating, remove the old dates, and add on the new ones (or calculate difference, either works depending on the integrity of this data)
When deleting, remove dates
Stalls
When creating, insert records for associated seasons
When deleting, delete records from availability collection
Bookings
When creating, add dates to bookedDuring
When updating, add or remove dates from bookedDuring
Then to find available stalls for a market, you can query where { marketId: /* market's ID */, availableDates.date: { $all: [/* each desired day */], bookedDuring: { $nin: [/* same dates */ ] } }} and then pluck out the stallId
And to find markets that have available, do { availableDates.dates: { $all: [/* each desired day */], bookedDuring: { $nin: [/* same dates */ ] } }} select distinct marketIds
I am executing nlapiSearchRecord function on itemfulfillment record and the search is working fine but its return result with repetition/duplicate.
Below is the my working code i just want to know that which filter should i use to avoid repetition/duplicate record.
var filters = [
new nlobjSearchFilter('internalid', null, 'is',id),
new nlobjSearchFilter('mainline', null, 'is', 'F'),
new nlobjSearchFilter('shipping', null, 'is', 'F'),
new nlobjSearchFilter('taxline', null, 'is', 'F')
], columns = [
new nlobjSearchColumn('trandate'),
new nlobjSearchColumn('tranid'),
new nlobjSearchColumn('item'),
new nlobjSearchColumn('quantity'),
new nlobjSearchColumn('location')
];
var searchresults = lapiSearchRecord('itemfulfillment',null, filters, columns);
Below is the search result for your understanding and you can see that there is one item but it's is duplicated two times with positive quantity and one time with negative quantity.
[{"id":"123","recordtype":"itemfulfillment",
"columns":{"trandate":"2/1/2222","tranid":"xx-xxxx","status":{"name":"xxxx","internalid":"xxxx"},"item":{"name":"Test-1","internalid":"1111"},"quantity":1,"location":{"name":"xxxx","internalid":"xxx"}}},
{"id":"123","recordtype":"itemfulfillment",
"columns":{"trandate":"2/1/2222","tranid":"xx-xxxx","status":{"name":"xxxx","internalid":"xxxx"},"item":{"name":"Test-1","internalid":"1111"},"quantity":1,"location":{"name":"xxxx","internalid":"xxx"}}},{"id":"123","recordtype":"itemfulfillment",
"columns":{"trandate":"2/1/2222","tranid":"xx-xxxx","status":{"name":"xxxx","internalid":"xxxx"},"item":{"name":"Test-1","internalid":"xxxx"},"quantity":-1,"location":{"name":"xxxx","internalid":"xx"}}},{"trandate":"2/1/2222","tranid":"xx-xxxx","status":{"name":"xxxx","internalid":"xxxx"},"item":{"name":"XXXXX","internalid":"1111"},"quantity":1,"location":{"name":"xxxx","internalid":"xxx"}}},
{"id":"123","recordtype":"itemfulfillment",
"columns":{"trandate":"2/1/2222","tranid":"xx-xxxx","status":{"name":"xxx","internalid":"xxxx"},"item":{"name":"Test-2","internalid":"xyz"},"quantity":2,"location":{"name":"xxx","internalid":"xxxx"}}},{"id":"123","recordtype":"itemfulfillment","columns":{"trandate":"2/1/2222","tranid":"xx-xxxx","status":{"name":"xxxx","internalid":"xxxx"},"item":{"name":"Test-2","internalid":"xyz"},"quantity":2,"location":{"name":"xxxx","internalid":"10"}}},{"id":"123","recordtype":"itemfulfillment",
"columns":{"trandate":"2/1/2222","tranid":"xx-xxxx","status":{"name":"xxxx","internalid":"xxxx"},"item":{"name":"Test-2","internalid":"xyz"},"quantity":-2,"location":{"name":"xxxx","internalid":"xxxx"}}}]
Can you please guide me that how can i get items without any repetition/duplication.
The positive and negative quantities relate to the G/L entries created by the fulfillment.
Choose the G/L account for the side of the transaction you are interested in as another filter.
When the billing schedule runs it auto generates invoices from sales order. When this happens - how can I create a link on the sales order that will allow me to load the corresponding invoice in code?
I need this so I can grab couple of field values from the invoice but I can't access the invoice directly from another entity which seems only related to sales order.
EDIT 1:
var fil = [];
fil[0] = new nlobjSearchFilter('createdfrom', null, 'is', nlapiGetRecordId())
var col = [];
col[0] = new nlobjSearchColumn('internalid');
var invoices = nlapiSearchRecord('invoice', null, fil, col);
nlapiLogExecution('DEBUG', 'field val', invoices);
Throws invalid operator or not in proper syntax: createdfrom.
Though adding a link on the Sales Order is a viable solution, it's not your only option. Alternatively, you could do a search for invoices where the createdfrom field is the internal ID of your Sales Order. Something like in SuiteScript 1.0:
var invoices = nlapiSearchRecord('invoice', null,
[['createdfrom', 'is', nlapiGetRecordId()]],
[/* create search columns for the fields you need off the invoice */]
) || [];
or in 2.0:
var invoices = search.create({
"type": search.Type.INVOICE,
"filters": [['createdfrom', 'is', context.currentRecord.id]],
"columns": [/* create search columns for the fields you need off the invoice */]
}).run().each(processResult);
This will get you a list of all the Invoices created from your Sales Order (which is likely only 1).
If you believe you need a link to the Invoice on the Sales Order, you could add the custom body field, then create a User Event on the Invoice record that populates this new field with its createdfrom value on the Before Submit event. But then what happens if your Sales Order gets paid via multiple Invoices?
I am very new to NoSQL. My usecase is related to this.... Many users post messages and we store it in cloudant as different documents
{
id:random,
userid:xxx,
timestamp: 1449216912282,
msg: "Hi..."
}
I want to find out users who have not posted anything for last 5 days - Additionally I want to know if they have posted anything between last five and 10 days. If they have, then send a reminder mail to user to be active.
Which option would be better - views, search, cloudant query? Assume we will be having 1000s of posts per hour
I though of creating view - map(userid,timestamp) reduce as _stats and get max timestamp of each user. Then iterating through this list - we get users who did not post in last 5 days.
Other option was to use search index, get all userids between required timestamps. Compare both lists in application.
Is there any way to do it in a single query without overloading the application? Would Changing data format or creating appropriate index or view help?
If your data looked like this:
{
"_id": "abcdefghijklmon1234",
"userid" : "user1",
"timestamp": 1449739485035,
"msg": "Hi"
}
You could create a MapReduce view that created an index with a key consisting of [ 2015, 50, "user1" ], where "2015" is the year, "50" is the week number and "user1" is the document's userid. This can be achieved with a Map function like this:
function (doc) {
var getWeek = function(t) {
var date = new Date(t);
date.setHours(0, 0, 0, 0);
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
var week1 = new Date(date.getFullYear(), 0, 4);
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
};
if (typeof doc.timestamp == "number") {
var d = new Date(doc.timestamp);
var weeknum = getWeek(d.getTime());
var year = d.getFullYear();
emit( [ year, weeknum, doc.userid], null);
}
}
with a reduce of "_count". This allows queries such as ?startkey=[2015,49]&endkey=[2015,50]&group_level=3 to get a list of users who DID post last week. The list of users who didn't are the users who don't appear in the above list.
This isn't a solution to your problem in terms of "in the last 5 days", but uses week numbers instead.
I'm running into problems getting aging information for a customer record via a Restlet. Specficially I'm looking to get the aging, aging1, aging2, aging3, and aging4 fields from the Customer form for a given customer.
In the UI those values are found on the Customer form under the "Financial" section and look something like:
Current 1-30 Days 31-60 Days 61-90 Days Over 90 Days
1200.00 800.00 720.37 423.23 42.00
My Restlet code looks something like this:
…
cols[6] = new nlobjSearchColumn('aging');
var result = nlapiSearchRecord(data.record_type, null, filter, cols);
return result;
It works great with other fields such as "balance", but when I include the "aging" field and run my GET I see this error:
"error": {
"code": "SSS_INVALID_SRCH_COL",
"message": "An nlobjSearchColumn contains an invalid column, or is not in proper syntax: aging."
}
Clearly I'm not doing something right. Are those fields special in some way? How do I retrieve those values?
As far as I recall there is no search column for customers called 'aging'. This is the cause for the Invalid Search column error.
Also those fields might not be exposed either to the searches or suitescript which is why you are getting the error.
I actually contacted Netsuite and the previous comments are correct, those fields are not exposed, so I simply can't use them.
There is currently an enhancement request #238854 to expose these fields.
Easy enough to add in a suitescript context. e.g.
var aging = nlapiSearchRecord('invoice', null, [
new nlobjSearchFilter('daysoverdue', null, 'greaterthan', 0),
new nlobjSearchFilter('mainline', null, 'is', 'T'),
new nlobjSearchFilter('amountremaining', null, 'greaterthan', 0)
//for your RESTLet maybe you need this: , new nlobjSearchFilter('entity', null, 'is', data.customer_id)
], [
new nlobjSearchColumn('entity', null, 'group'),
new nlobjSearchColumn('formulanumeric', null, 'sum').setFormula('case when {daysoverdue} < 31 then {amountremaining} else 0 end'),
new nlobjSearchColumn('formulanumeric', null, 'sum').setFormula('case when {daysoverdue} between 31 and 60 then {amountremaining} else 0 end'),
new nlobjSearchColumn('formulanumeric', null, 'sum').setFormula('case when {daysoverdue} between 61 and 90 then {amountremaining} else 0 end'),
new nlobjSearchColumn('formulanumeric', null, 'sum').setFormula('case when {daysoverdue} > 90 then {amountremaining} else 0 end')
]);
aging.forEach(function (inv) {
var cols = inv.getAllColumns();
console.log(inv.getText('entity', null, 'group') +
' 0-30: ' + inv.getValue(cols[1]) +
' 31-60: ' + inv.getValue(cols[2]) +
' 61-90: ' + inv.getValue(cols[3]) +
' > 90: ' + inv.getValue(cols[4]));
});
Just a followup note on this: After almost three years Netsuite has still not acted on that enhancement request even though it has been voted for 30 times. Also the solution above results in numbers that are usually, but not always, in line with what is displayed by Netsuite.