How to adjust the stock of a product tracked by lots in Odoo via API - odoo

OK so I have been banging my head at this problem for way too long by now.
I want to sync stock levels of a product that is tracked with lots between the webshop and Odoo. For this reason I need to be able to make a stock adjustment of a lot via the API (in this case in python).
I have found this possible way of doing it:
odoo(
'stock.move',
'create',
[{
"name": "Webshop stock adjustment",
"company_id": 1,
"location_id": 8, # warehouse
"location_dest_id": 14, # virtual location
"product_id": batch["product_id"][0],
"product_uom": 1,
"lot_ids": [batch["id"]], # I am searching for the id by the lot name beforehand
"product_uom_qty": 1,
"quantity_done": 1,
"state": "done"
}]
)
This, however, results in two moves! One move which has the correct lot, and another one without a specified lot. The latter move is faulty of course, as the product is tracked with lots. This results in a fault lot entry, where I can't change the quantity by hand, as the field is invalid. Worse, it results in wrong stock levels.
You can see the problematic bookings here
I have tried to just create a stock.move.line, like so:
odoo(
'stock.move.line',
'create',
[{
"company_id": 1,
"display_name": "Webshop adjustment", # does not appear
"location_id": location_id,
"location_dest_id": location_dest_id,
"product_id": batch["product_id"][0],
"product_uom_id": 1,
"lot_id": batch["id"],
"product_uom_qty": quantity,
"qty_done": quantity,
"state": "done" # has no effect
}]
)
However that results in a line with no effect: Line
I have also tried to find the stock adjustment wizard, but the only one I found in the code as opposed to the UI, doesn't have a field for lots..
I'd be happy for any input on how to solve this problem!

Meanwhile I managed to solve this problem reliably. I needed to implement a function for that, rather than mucking around with the external API.
The function here is expecting vals with the format below. It reduces whatever batch needs to go first.
[{
'sku': sku,
'qty': quantity
},]
#api.model
def reduce_lots(self, vals):
log(vals)
for product_req in vals:
product = self.env['product.product'].search(
[['default_code','=', product_req['sku']]]
)
if len(product) == 0:
continue
lots = self.env['stock.quant'].search(
['&',('product_id', '=', product[0]['id']),('on_hand', '=', True)],
order='removal_date asc'
)
move = self.env['stock.move'].create({
'name': product_req['order'],
'location_id': 8, # Our Warehouse
'location_dest_id': 14, # Virtual Location, Customer. If you need to increase stock, reverse the two numbers.
'product_id': product.id,
'product_uom': product.uom_id.id,
'product_uom_qty': product_req['qty'],
})
move._action_confirm()
move._action_assign()
product_req['lots'] = []
for line in move.move_line_ids:
line.write({'qty_done': line['product_uom_qty']})
product_req['lots'].append({
'_qty': line['product_uom_qty'],
'_lot_id': line.lot_id.name,
'_best_before': line.lot_id.removal_date
})
move._action_done()
return vals

Related

To get the weight of product in ebay api

Currenly i am working in ebaysdk. I am facing the problem which is the weight of the product. how can i can get product weight ? I used trading api but most of weight of the products equal to 0. is there any way to get every product weight? I requested like this:
response = api_trading.execute('GetItem' ,
{"ItemID":"184097524395",'DestinationPostalCode':'2940','DestinationCountryCode':'GB'})
Some items have their weight included in the "Items Specifics" section, which can only be extracted by setting "IncludeItemSpecifics" to "true" in the trading api execution:
response = api_trading.execute('GetItem' , {"ItemID":"254350593466", "IncludeItemSpecifics": "true"})
Then, one possible way to get the details of interest is by looping through the dictionary:
for d in response.dict()['Item']['ItemSpecifics']['NameValueList']:
print(d)
The weight Name and Value will be in one of those dictionaries:
...
{'Name': 'Item Length', 'Value': '1', 'Source': 'ItemSpecific'}
{'Name': 'Item Weight', 'Value': '2.25', 'Source': 'ItemSpecific'}
{'Name': 'Item Width', 'Value': '1', 'Source': 'ItemSpecific'}
...
Source:
https://developer.ebay.com/devzone/guides/features-guide/default.html#development/ItemSpecifics.html
It seems like if the seller does not indicate a weight in the first place, the api has nothing to show for it.
If shipping is free, most likely the seller does not bother entering the weight of the item. So perhaps find those products whose shipping is not free, maybe the seller will indicate the weight for shipping calculations.
"ShippingDetails": {"ShippingType": "Calculated"}
I also tried GetItem and it can show weight of the item as long as weight is available. I also tried GetItemShipping and it can show weight of item if available, but needs DestinationPostalCode.
Souce:https://github.com/timotheus/ebaysdk-python/issues/304

MongoDB using $and with slice and a match

I'm using #Query from the spring data package and I want to query on the last element of an array in a document.
For example the data structure could be like this:
{
name : 'John',
scores: [10, 12, 14, 16]
},
{
name : 'Mary',
scores: [78, 20, 14]
},
So I've built a query, however it is complaining that "error message 'unknown operator: $slice' on server"
The $slice part of the query, when run separately, is fine:
db.getCollection('users').find({}, {scores: { $slice: -1 })
However as soon as I combine it with a more complex check, it gives the error as mentioned.
db.getCollection('users').find{{"$and":[{ } , {"scores" : { "$slice" : -1}} ,{"scores": "16"}]})
This query would return the list of users who had a last score of 16, in my example John would be returned but not Mary.
I've put it into a standard mongo query (to debug things), however ideally I need it to go into a spring-data #query construct - they should be fairly similar.
Is there anyway of doing this, without resorting to hand-cranked java calls? I don't see much documentation for #Query, other than it takes standard queries.
As commented with the link post, that refers to aggregate, how does that work with #Query, plus one of the main answers uses $where, this inefficient.
The general way forward with the problem is unfortunately the data, although #Veeram's response is correct, it will mean that you do not hit indexes. This is an issue where you've got very large data sets of course and you will see ever decreasing return times. It's something $where, $arrayElemAt cannot help you with. They have to pre-process the data and that means a full collection scan. We analysed several queries with these constructs and they involved a "COLSCAN".
The solution is ideally to create a field that contains the last item, for instance:
{
name : 'John',
scores: [10, 12, 14, 16],
lastScore: 16
},
{
name : 'Mary',
scores: [78, 20, 14],
lastScore: 14
}
You could create a listener to maintain this as follows:
#Component
public class ScoreListener extends AbstractMongoEventListener<Scores>
You then get the ability to sniff the data and make any updates:
#Override
public void onBeforeConvert(BeforeConvertEvent<Scores> event) {
// process any score and set lastScore
}
Don't forget to update your indexes (!):
#CompoundIndex(name = "lastScore", def = "{"
+ "'lastScore': 1"
+ " }")
Although this does contain a disadvantage of a slight duplication of data, in current Mongo (3.4) this really is the only way of doing this AND to include indexes in the search mechanism. The speed differences were dramatic, from nearly a minute response time down to milliseconds.
In Mongo 3.6 there may be better ways for doing that, however we are fixed on this version, so this has to be our solution.

get grouped results with sparql query

I still feel like a SPARQL newbie, so I may be way off base about what SPARQL GROUP BY does, but here's my questions.
Suppose I wanted to request all resources in graph database called Categories, and I wanted to get all the items associated with these categories, along with the names of the items and their price.
Right now my SPARQL queries are giving me back something like the following table:
**Categories Item ItemName ItemPrice**
Tools HammerID Hammer $12
Tools SawID Saw $13
Tools WrenchID Wrench $10
Food AppleID Apple $5
Food CornID Corn $1
I wanted to use GROUP BY to group the items under a single category, so that when I start processing it, I can look through each unique category and then display the items that belong in that category.
Right now if I loop through the above results, I will be iterating over 5 entries instead of 2.
The other way I can describe the results I want are by imaging what the corresponding json data would look like. I want something like:
[
tools: [
{id: hammerId
title: hammer
price: $12},
{id: sawId
title: saw
price: $13},
{id: wrenchId
title: wrench
price: $10}
],
food: [
{id: appleId
title: apple
price: $5},
{id: cornId
title: corn
price: $1}
]
]
With the results, like this I can directly loop over the top level items, and then display the results for each.
Can I use GROUP BY to tell SPARQL to give me results like this?
No, you can't. A SPARQL SELECT query-result is defined as a sequence of solutions, with each solution being a set of variable-value pairs (with a value being defined as an IRI, BNode, or literal value). Basically it's a simple table. There is no provision for 'nested' solutions like you'd need for your JSON-like structure.
However the difference is purely syntactic. If you group, you know the result will deliver all solutions belonging to the same group together (one after the other) - so in processing the result you can simply treat the grouped variable as a marker. And of course if you really want, you can easily rewrite the query result into this kind of syntactic structure yourself - it's just a different way of writing down the exact same information, after all.

BigQuery UDF Internal Error

We had a simple UDF in BigQuery that somehow throws an error that keeps returning
Query Failed
Error: An internal error occurred and the request could not be completed.
The query was simply trying to use UDF to perform a SHA256.
SELECT
input AS title,
input_sha256 AS title_sha256
FROM
SHA256(
SELECT
title AS input
FROM
[bigquery-public-data:hacker_news.stories]
GROUP BY
input
)
LIMIT
1000
The in-line UDF is pasted below. However I can not post the full UDF as StackOverflow complaints too much code in the post. The full UDF can be seen this gist.
function sha256(row, emit) {
emit(
{
input: row.input,
input_sha256: CryptoJS.SHA256(row.input).toString(CryptoJS.enc.Hex)
}
);
}
bigquery.defineFunction(
'SHA256', // Name of the function exported to SQL
['input'], // Names of input columns
[
{'name': 'input', 'type': 'string'},
{'name': 'input_sha256', 'type': 'string'}
],
sha256 // Reference to JavaScript UDF
);
Not sure if it helps, but the Job-ID is
bigquery:bquijob_7fd3b51c_153c058dc7c
Looks like there is a similar issue at:
https://code.google.com/p/google-bigquery/issues/detail?id=478
Short answer - this is an issue related to memory allocation that I uncovered via my own testing and fixed today, but it will take a little while to flow out to production.
Slightly longer answer - we just rolled out a fix today for an issue where users who were having "out of memory" issues when scaling up their UDFs over larger number of rows, even though the UDF would succeed on smaller numbers of rows. The queries that were hitting that condition are now running fine on our internal / test trees. However, since public BigQuery hosts have much higher traffic loads, the JavaScript engine that executes the UDFs (V8) behaves somewhat differently in production than it does in internal trees. Specifically, there's a new memory allocation error that some of the previously OOMing jobs are now hitting that we couldn't observe until the queries ran on a fully-loaded tree.
It's a minor error with a quick fix, but we'd ideally let it flow through our regular testing and QA cycle. This should put the fix in production in about a week, assuming nothing else goes wrong with the candidate. Would that be acceptable for you?
i am re-using answer box to provide full query text. it works if uncomment LIMIT 40
SELECT input, input_sha256 FROM JS(
(
SELECT title AS input
FROM [bigquery-public-data:hacker_news.stories]
GROUP BY input
//LIMIT 40
),
input,
"[ {'name': 'input', 'type': 'string'}, {'name': 'input_sha256', 'type': 'string'} ] ",
"function(row, emit) {
var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty('init')||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty('toString')&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e<a;e++)c[b+e>>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535<d.length)for(e=0;e<a;e+=4)c[b+e>>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d<a;d+=4)c.push(4294967296*h.random()|0);return new r.init(c,a)}}),l=f.enc={},k=l.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++){var e=c[b>>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join('')},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b+=2)d[b>>>3]|=parseInt(a.substr(b, 2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++)d.push(String.fromCharCode(c[b>>>2]>>>24-8*(b%4)&255));return d.join('')},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b++)d[b>>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error('Malformed UTF-8 data');}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}}, u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){'string'==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;g<a;g+=e)this._doProcessBlock(d,g);g=d.splice(0,a);c.sigBytes-=b}return new r.init(g,b)},clone:function(){var a=m.clone.call(this); a._data=this._data.clone();return a},_minBufferSize:0});g.Hasher=u.extend({cfg:m.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){u.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,d){return(new a.init(d)).finalize(c)}},_createHmacHelper:function(a){return function(c,d){return(new t.HMAC.init(a, d)).finalize(c)}}});var t=f.algo={};return f}(Math);
(function(h){for(var s=CryptoJS,f=s.lib,g=f.WordArray,q=f.Hasher,f=s.algo,m=[],r=[],l=function(a){return 4294967296*(a-(a|0))|0},k=2,n=0;64>n;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]= c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math);
(function(){var h=CryptoJS,j=h.lib.WordArray;h.enc.Base64={stringify:function(b){var e=b.words,f=b.sigBytes,c=this._map;b.clamp();b=[];for(var a=0;a<f;a+=3)for(var d=(e[a>>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g<f;g++)b.push(c.charAt(d>>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join('')},parse:function(b){var e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));for(var c=[],a=0,d=0;d< e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return j.create(c,a)},_map:'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='}})();
emit( { input: row.input, input_sha256: CryptoJS.SHA256(row.input).toString(CryptoJS.enc.Hex) } );
}"
)

eBay API categoryId in findItemsAdvanced call returns wrong categories

I'm trying to use the categoryId in my findItemsAdvanced query:
api.execute('findItemsAdvanced', {
'keywords': 'laptop',
'categoryId': '51148'}
The results I get are, for example (printing the searchResult dictionary):
'itemId': {'value': '200971548007'}, 'isMultiVariationListing': .............
'primaryCategory': {'categoryId': {'value': '69202'}, 'categoryName': {'value': 'Air Conditioning'}}
....."
You can see that the result has a categoryId of 69202, and not 51148.
What am I doing wrong here? I'm just using the finding.py code at:
https://github.com/timotheus/ebaysdk-python
Thanks
Edit
I've done some tests. I extracted the XML that the SDK builds. If I call with:
'categoryId': '177'
The response is:
the request_xml is <?xml version='1.0' encoding='utf-8'?><findItemsAdvancedRequest
xmlns="http://www.ebay.com/marketplace/search/v1/services"><categoryId>177</categoryId>
<itemFilter><name>Condition</name><value>Used</value></itemFilter><itemFilter>
<name>LocatedIn</name><value>GB</value></itemFilter><keywords>laptop</keywords>
<paginationInput><entriesPerPage>100</entriesPerPage><pageNumber>1</pageNumber>
</paginationInput></findItemsAdvancedRequest>
and I get the same with
'categoryId': ['177']
I find this a bit odd, I thought the appropriate name for the XML categoryId was 'CategoryId' with a capital C. If I do that I don't get an error, but the result is not restricted to the categoryId requested.
Doing it like above, I still get the error:
Exception: findItemsAdvanced: Domain: Marketplace, Severity: Error,
errorId: 3, Invalid category ID.
The code below will do a keyword search for 'laptops' across the UK eBay site and restrict the search to the two categories Apple Laptops(111422) and PC Laptops & Netbooks(177). In addition the results are filtered to only show the first 25 used items that are priced between £200 and £400. The results are also sorted by price from high to low.
There are a few things to keep in mind about this example.
It assumes that you have already installed ebaysdk-python.
According to the eBay docs the categoryId field is a string and more than one category can be specified. An array is therefore used to hold the category ids that we are interested in.
Our request needs to search for items in the UK eBay site. We therefore pass EBAY-GB as the siteid parameter.
Category ids are different across each eBay site. For example the category PC Laptops & Netbooks(177) does not exist in Belgium. (Which incidently is the site that is used in the ebaysdk-python finding.py example.)
This example is also available as a Gist
import ebaysdk
from ebaysdk import finding
api = finding(siteid='EBAY-GB', appid='<REPLACE WITH YOUR OWN APPID>')
api.execute('findItemsAdvanced', {
'keywords': 'laptop',
'categoryId' : ['177', '111422'],
'itemFilter': [
{'name': 'Condition', 'value': 'Used'},
{'name': 'MinPrice', 'value': '200', 'paramName': 'Currency', 'paramValue': 'GBP'},
{'name': 'MaxPrice', 'value': '400', 'paramName': 'Currency', 'paramValue': 'GBP'}
],
'paginationInput': {
'entriesPerPage': '25',
'pageNumber': '1'
},
'sortOrder': 'CurrentPriceHighest'
})
dictstr = api.response_dict()
for item in dictstr['searchResult']['item']:
print "ItemID: %s" % item['itemId'].value
print "Title: %s" % item['title'].value
print "CategoryID: %s" % item['primaryCategory']['categoryId'].value
I hope the following will explain why performing a search on the Belgium site results in items that contain the category 177 even though this is not valid for Belgium but is valid for the UK.
Basically eBay allow sellers from one site to appear in the search results of another site as long as they meet the required criteria, such as offering international shipping. It allows sellers to sell to other countries without the need to actually list on those sites.
From the example XML that elelias provided I can see that a keyword search for 'laptop' was made on the Belgium site with the results filtered so that only items located in the UK was to be returned.
<itemFilter>
<name>LocatedIn</name>
<value>GB</value>
</itemFilter>
Because the search was limited to those located in the UK you won't see any Belgium items in the results. Since the items where listed on the UK site they will contain information relevant to the UK. For example the category id 177. eBay does not convert the information to make it relevant to the site that you are searching on.
It is important to remember that what ever you are trying to do with the Finding API can also be repeated using the actual advance search on eBay. For example it is possible to re-create the issue by performing a keyword search for used items on the Belgium site.
This url is the equivalent of your code that was performing the search without specifying the category 177. As you can see from the results it returns items that where listed on the UK site but which are appearing in the Belgium site. It you click on some of the items, for example, you can even see that it displays the UK category PC Laptops & Netbooks (177) even though this does not exist on the Belgium site. This matches the results form your code where it was returning 177 but would not let you specify the same value in the request as you was searching the Belgium site.
I hope this helps.
Because categoryId is repeatable. You will need to pass an array into the call. Something like this should work.
api.execute('findItemsAdvanced', {
'keywords': 'laptop',
'categoryId': [
{'51148'}
]
}
Note: See how the itemFilter element is an array in the sample file of the SDK.
'itemFilter': [
{'name': 'Condition',
'value': 'Used'},
{'name': 'LocatedIn',
'value': 'GB'},
],