mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
4a62911696
3
.github/workflows/welcome.yml
vendored
3
.github/workflows/welcome.yml
vendored
@ -9,6 +9,9 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
run:
|
run:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/first-interaction@v1
|
- uses: actions/first-interaction@v1
|
||||||
with:
|
with:
|
||||||
|
@ -306,6 +306,10 @@ if DEBUG and CONFIG.get('debug_toolbar', False): # pragma: no cover
|
|||||||
INSTALLED_APPS.append('debug_toolbar')
|
INSTALLED_APPS.append('debug_toolbar')
|
||||||
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
|
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
|
||||||
|
|
||||||
|
# Allow secure http developer server in debug mode
|
||||||
|
if DEBUG:
|
||||||
|
INSTALLED_APPS.append('sslserver')
|
||||||
|
|
||||||
# InvenTree URL configuration
|
# InvenTree URL configuration
|
||||||
|
|
||||||
# Base URL for admin pages (default="admin")
|
# Base URL for admin pages (default="admin")
|
||||||
|
99
InvenTree/InvenTree/static/script/qr-scanner-worker.min.js
vendored
Normal file
99
InvenTree/InvenTree/static/script/qr-scanner-worker.min.js
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*! qr-scanner v1.4.1 https://github.com/nimiq/qr-scanner Licensed MIT */
|
||||||
|
export const createWorker=()=>new Worker(URL.createObjectURL(new Blob([`class x{constructor(a,b){this.width=b;this.height=a.length/b;this.data=a}static createEmpty(a,b){return new x(new Uint8ClampedArray(a*b),a)}get(a,b){return 0>a||a>=this.width||0>b||b>=this.height?!1:!!this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c?1:0}setRegion(a,b,c,d,e){for(let f=b;f<b+d;f++)for(let g=a;g<a+c;g++)this.set(g,f,!!e)}}
|
||||||
|
class A{constructor(a,b,c){this.width=a;a*=b;if(c&&c.length!==a)throw Error("Wrong buffer size");this.data=c||new Uint8ClampedArray(a)}get(a,b){return this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c}}
|
||||||
|
class ba{constructor(a){this.bitOffset=this.byteOffset=0;this.bytes=a}readBits(a){if(1>a||32<a||a>this.available())throw Error("Cannot read "+a.toString()+" bits");var b=0;if(0<this.bitOffset){b=8-this.bitOffset;var c=a<b?a:b;b-=c;b=(this.bytes[this.byteOffset]&255>>8-c<<b)>>b;a-=c;this.bitOffset+=c;8===this.bitOffset&&(this.bitOffset=0,this.byteOffset++)}if(0<a){for(;8<=a;)b=b<<8|this.bytes[this.byteOffset]&255,this.byteOffset++,a-=8;0<a&&(c=8-a,b=b<<a|(this.bytes[this.byteOffset]&255>>c<<c)>>c,
|
||||||
|
this.bitOffset+=a)}return b}available(){return 8*(this.bytes.length-this.byteOffset)-this.bitOffset}}var B,C=B||(B={});C.Numeric="numeric";C.Alphanumeric="alphanumeric";C.Byte="byte";C.Kanji="kanji";C.ECI="eci";C.StructuredAppend="structuredappend";var D,E=D||(D={});E[E.Terminator=0]="Terminator";E[E.Numeric=1]="Numeric";E[E.Alphanumeric=2]="Alphanumeric";E[E.Byte=4]="Byte";E[E.Kanji=8]="Kanji";E[E.ECI=7]="ECI";E[E.StructuredAppend=3]="StructuredAppend";let F="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");
|
||||||
|
function ca(a,b){let c=[],d="";b=a.readBits([8,16,16][b]);for(let e=0;e<b;e++){let f=a.readBits(8);c.push(f)}try{d+=decodeURIComponent(c.map(e=>\`%\${("0"+e.toString(16)).substr(-2)}\`).join(""))}catch(e){}return{bytes:c,text:d}}
|
||||||
|
function da(a,b){a=new ba(a);let c=9>=b?0:26>=b?1:2;for(b={text:"",bytes:[],chunks:[],version:b};4<=a.available();){var d=a.readBits(4);if(d===D.Terminator)return b;if(d===D.ECI)0===a.readBits(1)?b.chunks.push({type:B.ECI,assignmentNumber:a.readBits(7)}):0===a.readBits(1)?b.chunks.push({type:B.ECI,assignmentNumber:a.readBits(14)}):0===a.readBits(1)?b.chunks.push({type:B.ECI,assignmentNumber:a.readBits(21)}):b.chunks.push({type:B.ECI,assignmentNumber:-1});else if(d===D.Numeric){var e=a,f=[];d="";for(var g=
|
||||||
|
e.readBits([10,12,14][c]);3<=g;){var h=e.readBits(10);if(1E3<=h)throw Error("Invalid numeric value above 999");var k=Math.floor(h/100),m=Math.floor(h/10)%10;h%=10;f.push(48+k,48+m,48+h);d+=k.toString()+m.toString()+h.toString();g-=3}if(2===g){g=e.readBits(7);if(100<=g)throw Error("Invalid numeric value above 99");e=Math.floor(g/10);g%=10;f.push(48+e,48+g);d+=e.toString()+g.toString()}else if(1===g){e=e.readBits(4);if(10<=e)throw Error("Invalid numeric value above 9");f.push(48+e);d+=e.toString()}b.text+=
|
||||||
|
d;b.bytes.push(...f);b.chunks.push({type:B.Numeric,text:d})}else if(d===D.Alphanumeric){e=a;f=[];d="";for(g=e.readBits([9,11,13][c]);2<=g;)m=e.readBits(11),k=Math.floor(m/45),m%=45,f.push(F[k].charCodeAt(0),F[m].charCodeAt(0)),d+=F[k]+F[m],g-=2;1===g&&(e=e.readBits(6),f.push(F[e].charCodeAt(0)),d+=F[e]);b.text+=d;b.bytes.push(...f);b.chunks.push({type:B.Alphanumeric,text:d})}else if(d===D.Byte)d=ca(a,c),b.text+=d.text,b.bytes.push(...d.bytes),b.chunks.push({type:B.Byte,bytes:d.bytes,text:d.text});
|
||||||
|
else if(d===D.Kanji){f=a;d=[];e=f.readBits([8,10,12][c]);for(g=0;g<e;g++)k=f.readBits(13),k=Math.floor(k/192)<<8|k%192,k=7936>k?k+33088:k+49472,d.push(k>>8,k&255);f=(new TextDecoder("shift-jis")).decode(Uint8Array.from(d));b.text+=f;b.bytes.push(...d);b.chunks.push({type:B.Kanji,bytes:d,text:f})}else d===D.StructuredAppend&&b.chunks.push({type:B.StructuredAppend,currentSequence:a.readBits(4),totalSequence:a.readBits(4),parity:a.readBits(8)})}if(0===a.available()||0===a.readBits(a.available()))return b}
|
||||||
|
class G{constructor(a,b){if(0===b.length)throw Error("No coefficients.");this.field=a;let c=b.length;if(1<c&&0===b[0]){let d=1;for(;d<c&&0===b[d];)d++;if(d===c)this.coefficients=a.zero.coefficients;else for(this.coefficients=new Uint8ClampedArray(c-d),a=0;a<this.coefficients.length;a++)this.coefficients[a]=b[d+a]}else this.coefficients=b}degree(){return this.coefficients.length-1}isZero(){return 0===this.coefficients[0]}getCoefficient(a){return this.coefficients[this.coefficients.length-1-a]}addOrSubtract(a){if(this.isZero())return a;
|
||||||
|
if(a.isZero())return this;let b=this.coefficients;a=a.coefficients;b.length>a.length&&([b,a]=[a,b]);let c=new Uint8ClampedArray(a.length),d=a.length-b.length;for(var e=0;e<d;e++)c[e]=a[e];for(e=d;e<a.length;e++)c[e]=b[e-d]^a[e];return new G(this.field,c)}multiply(a){if(0===a)return this.field.zero;if(1===a)return this;let b=this.coefficients.length,c=new Uint8ClampedArray(b);for(let d=0;d<b;d++)c[d]=this.field.multiply(this.coefficients[d],a);return new G(this.field,c)}multiplyPoly(a){if(this.isZero()||
|
||||||
|
a.isZero())return this.field.zero;let b=this.coefficients,c=b.length;a=a.coefficients;let d=a.length,e=new Uint8ClampedArray(c+d-1);for(let f=0;f<c;f++){let g=b[f];for(let h=0;h<d;h++)e[f+h]=H(e[f+h],this.field.multiply(g,a[h]))}return new G(this.field,e)}multiplyByMonomial(a,b){if(0>a)throw Error("Invalid degree less than 0");if(0===b)return this.field.zero;let c=this.coefficients.length;a=new Uint8ClampedArray(c+a);for(let d=0;d<c;d++)a[d]=this.field.multiply(this.coefficients[d],b);return new G(this.field,
|
||||||
|
a)}evaluateAt(a){let b=0;if(0===a)return this.getCoefficient(0);let c=this.coefficients.length;if(1===a)return this.coefficients.forEach(d=>{b^=d}),b;b=this.coefficients[0];for(let d=1;d<c;d++)b=H(this.field.multiply(a,b),this.coefficients[d]);return b}}function H(a,b){return a^b}
|
||||||
|
class ea{constructor(a,b,c){this.primitive=a;this.size=b;this.generatorBase=c;this.expTable=Array(this.size);this.logTable=Array(this.size);a=1;for(b=0;b<this.size;b++)this.expTable[b]=a,a*=2,a>=this.size&&(a=(a^this.primitive)&this.size-1);for(a=0;a<this.size-1;a++)this.logTable[this.expTable[a]]=a;this.zero=new G(this,Uint8ClampedArray.from([0]));this.one=new G(this,Uint8ClampedArray.from([1]))}multiply(a,b){return 0===a||0===b?0:this.expTable[(this.logTable[a]+this.logTable[b])%(this.size-1)]}inverse(a){if(0===
|
||||||
|
a)throw Error("Can't invert 0");return this.expTable[this.size-this.logTable[a]-1]}buildMonomial(a,b){if(0>a)throw Error("Invalid monomial degree less than 0");if(0===b)return this.zero;a=new Uint8ClampedArray(a+1);a[0]=b;return new G(this,a)}log(a){if(0===a)throw Error("Can't take log(0)");return this.logTable[a]}exp(a){return this.expTable[a]}}
|
||||||
|
function fa(a,b,c,d){b.degree()<c.degree()&&([b,c]=[c,b]);let e=a.zero;for(var f=a.one;c.degree()>=d/2;){var g=b;let h=e;b=c;e=f;if(b.isZero())return null;c=g;f=a.zero;g=b.getCoefficient(b.degree());for(g=a.inverse(g);c.degree()>=b.degree()&&!c.isZero();){let k=c.degree()-b.degree(),m=a.multiply(c.getCoefficient(c.degree()),g);f=f.addOrSubtract(a.buildMonomial(k,m));c=c.addOrSubtract(b.multiplyByMonomial(k,m))}f=f.multiplyPoly(e).addOrSubtract(h);if(c.degree()>=b.degree())return null}d=f.getCoefficient(0);
|
||||||
|
if(0===d)return null;a=a.inverse(d);return[f.multiply(a),c.multiply(a)]}
|
||||||
|
function ha(a,b){let c=new Uint8ClampedArray(a.length);c.set(a);a=new ea(285,256,0);var d=new G(a,c),e=new Uint8ClampedArray(b),f=!1;for(var g=0;g<b;g++){var h=d.evaluateAt(a.exp(g+a.generatorBase));e[e.length-1-g]=h;0!==h&&(f=!0)}if(!f)return c;d=new G(a,e);d=fa(a,a.buildMonomial(b,1),d,b);if(null===d)return null;b=d[0];g=b.degree();if(1===g)b=[b.getCoefficient(1)];else{e=Array(g);f=0;for(h=1;h<a.size&&f<g;h++)0===b.evaluateAt(h)&&(e[f]=a.inverse(h),f++);b=f!==g?null:e}if(null==b)return null;e=d[1];
|
||||||
|
f=b.length;d=Array(f);for(g=0;g<f;g++){h=a.inverse(b[g]);let k=1;for(let m=0;m<f;m++)g!==m&&(k=a.multiply(k,H(1,a.multiply(b[m],h))));d[g]=a.multiply(e.evaluateAt(h),a.inverse(k));0!==a.generatorBase&&(d[g]=a.multiply(d[g],h))}for(e=0;e<b.length;e++){f=c.length-1-a.log(b[e]);if(0>f)return null;c[f]^=d[e]}return c}
|
||||||
|
let I=[{infoBits:null,versionNumber:1,alignmentPatternCenters:[],errorCorrectionLevels:[{ecCodewordsPerBlock:7,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:13,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:13}]},{ecCodewordsPerBlock:17,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:9}]}]},{infoBits:null,versionNumber:2,alignmentPatternCenters:[6,18],errorCorrectionLevels:[{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,
|
||||||
|
dataCodewordsPerBlock:34}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:28}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]}]},{infoBits:null,versionNumber:3,alignmentPatternCenters:[6,22],errorCorrectionLevels:[{ecCodewordsPerBlock:15,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:55}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:18,
|
||||||
|
ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:13}]}]},{infoBits:null,versionNumber:4,alignmentPatternCenters:[6,26],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:80}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:32}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:9}]}]},
|
||||||
|
{infoBits:null,versionNumber:5,alignmentPatternCenters:[6,30],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:43}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:11},{numBlocks:2,dataCodewordsPerBlock:12}]}]},{infoBits:null,versionNumber:6,alignmentPatternCenters:[6,
|
||||||
|
34],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:27}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:31892,versionNumber:7,alignmentPatternCenters:[6,22,38],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:78}]},{ecCodewordsPerBlock:18,
|
||||||
|
ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:31}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:13},{numBlocks:1,dataCodewordsPerBlock:14}]}]},{infoBits:34236,versionNumber:8,alignmentPatternCenters:[6,24,42],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:97}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:38},
|
||||||
|
{numBlocks:2,dataCodewordsPerBlock:39}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:18},{numBlocks:2,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:14},{numBlocks:2,dataCodewordsPerBlock:15}]}]},{infoBits:39577,versionNumber:9,alignmentPatternCenters:[6,26,46],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:36},
|
||||||
|
{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:12},{numBlocks:4,dataCodewordsPerBlock:13}]}]},{infoBits:42195,versionNumber:10,alignmentPatternCenters:[6,28,50],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68},{numBlocks:2,dataCodewordsPerBlock:69}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,
|
||||||
|
dataCodewordsPerBlock:43},{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]}]},{infoBits:48118,versionNumber:11,alignmentPatternCenters:[6,30,54],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:81}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1,
|
||||||
|
dataCodewordsPerBlock:50},{numBlocks:4,dataCodewordsPerBlock:51}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:22},{numBlocks:4,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:12},{numBlocks:8,dataCodewordsPerBlock:13}]}]},{infoBits:51042,versionNumber:12,alignmentPatternCenters:[6,32,58],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:92},{numBlocks:2,dataCodewordsPerBlock:93}]},
|
||||||
|
{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:36},{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:20},{numBlocks:6,dataCodewordsPerBlock:21}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:55367,versionNumber:13,alignmentPatternCenters:[6,34,62],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:107}]},
|
||||||
|
{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:37},{numBlocks:1,dataCodewordsPerBlock:38}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:20},{numBlocks:4,dataCodewordsPerBlock:21}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:11},{numBlocks:4,dataCodewordsPerBlock:12}]}]},{infoBits:58893,versionNumber:14,alignmentPatternCenters:[6,26,46,66],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:115},
|
||||||
|
{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:40},{numBlocks:5,dataCodewordsPerBlock:41}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:16},{numBlocks:5,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:5,dataCodewordsPerBlock:13}]}]},{infoBits:63784,versionNumber:15,alignmentPatternCenters:[6,26,48,70],errorCorrectionLevels:[{ecCodewordsPerBlock:22,
|
||||||
|
ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:87},{numBlocks:1,dataCodewordsPerBlock:88}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:41},{numBlocks:5,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:7,dataCodewordsPerBlock:13}]}]},{infoBits:68472,versionNumber:16,alignmentPatternCenters:[6,26,50,
|
||||||
|
74],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:98},{numBlocks:1,dataCodewordsPerBlock:99}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:70749,
|
||||||
|
versionNumber:17,alignmentPatternCenters:[6,30,54,78],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:1,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22},{numBlocks:15,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:17,
|
||||||
|
dataCodewordsPerBlock:15}]}]},{infoBits:76311,versionNumber:18,alignmentPatternCenters:[6,30,56,82],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:120},{numBlocks:1,dataCodewordsPerBlock:121}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:43},{numBlocks:4,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},{numBlocks:1,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,
|
||||||
|
dataCodewordsPerBlock:14},{numBlocks:19,dataCodewordsPerBlock:15}]}]},{infoBits:79154,versionNumber:19,alignmentPatternCenters:[6,30,58,86],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:113},{numBlocks:4,dataCodewordsPerBlock:114}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:44},{numBlocks:11,dataCodewordsPerBlock:45}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:21},{numBlocks:4,dataCodewordsPerBlock:22}]},
|
||||||
|
{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:13},{numBlocks:16,dataCodewordsPerBlock:14}]}]},{infoBits:84390,versionNumber:20,alignmentPatternCenters:[6,34,62,90],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:41},{numBlocks:13,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},
|
||||||
|
{numBlocks:5,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:15},{numBlocks:10,dataCodewordsPerBlock:16}]}]},{infoBits:87683,versionNumber:21,alignmentPatternCenters:[6,28,50,72,94],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:116},{numBlocks:4,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},
|
||||||
|
{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:16},{numBlocks:6,dataCodewordsPerBlock:17}]}]},{infoBits:92361,versionNumber:22,alignmentPatternCenters:[6,26,50,74,98],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:111},{numBlocks:7,dataCodewordsPerBlock:112}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},
|
||||||
|
{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:13}]}]},{infoBits:96236,versionNumber:23,alignmentPatternCenters:[6,30,54,74,102],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:121},{numBlocks:5,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:47},{numBlocks:14,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},
|
||||||
|
{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:16,dataCodewordsPerBlock:15},{numBlocks:14,dataCodewordsPerBlock:16}]}]},{infoBits:102084,versionNumber:24,alignmentPatternCenters:[6,28,54,80,106],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:45},{numBlocks:14,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,
|
||||||
|
ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:30,dataCodewordsPerBlock:16},{numBlocks:2,dataCodewordsPerBlock:17}]}]},{infoBits:102881,versionNumber:25,alignmentPatternCenters:[6,32,58,84,110],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:106},{numBlocks:4,dataCodewordsPerBlock:107}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:47},{numBlocks:13,
|
||||||
|
dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:110507,versionNumber:26,alignmentPatternCenters:[6,30,58,86,114],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:114},{numBlocks:2,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,
|
||||||
|
dataCodewordsPerBlock:46},{numBlocks:4,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:28,dataCodewordsPerBlock:22},{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:33,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]}]},{infoBits:110734,versionNumber:27,alignmentPatternCenters:[6,34,62,90,118],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},
|
||||||
|
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:23},{numBlocks:26,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:15},{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:117786,versionNumber:28,alignmentPatternCenters:[6,26,50,74,98,122],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:117},
|
||||||
|
{numBlocks:10,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:45},{numBlocks:23,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:24},{numBlocks:31,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:31,dataCodewordsPerBlock:16}]}]},{infoBits:119615,versionNumber:29,alignmentPatternCenters:[6,30,54,78,102,126],errorCorrectionLevels:[{ecCodewordsPerBlock:30,
|
||||||
|
ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:116},{numBlocks:7,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:21,dataCodewordsPerBlock:45},{numBlocks:7,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:23},{numBlocks:37,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:15},{numBlocks:26,dataCodewordsPerBlock:16}]}]},{infoBits:126325,versionNumber:30,alignmentPatternCenters:[6,
|
||||||
|
26,52,78,104,130],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:115},{numBlocks:10,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:47},{numBlocks:10,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},{numBlocks:25,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},{numBlocks:25,dataCodewordsPerBlock:16}]}]},
|
||||||
|
{infoBits:127568,versionNumber:31,alignmentPatternCenters:[6,30,56,82,108,134],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:3,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:46},{numBlocks:29,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:24},{numBlocks:1,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},
|
||||||
|
{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:133589,versionNumber:32,alignmentPatternCenters:[6,34,60,86,112,138],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:24},{numBlocks:35,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,
|
||||||
|
dataCodewordsPerBlock:15},{numBlocks:35,dataCodewordsPerBlock:16}]}]},{infoBits:136944,versionNumber:33,alignmentPatternCenters:[6,30,58,86,114,142],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115},{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:21,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:24},{numBlocks:19,dataCodewordsPerBlock:25}]},
|
||||||
|
{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:141498,versionNumber:34,alignmentPatternCenters:[6,34,62,90,118,146],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:6,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:44,
|
||||||
|
dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:59,dataCodewordsPerBlock:16},{numBlocks:1,dataCodewordsPerBlock:17}]}]},{infoBits:145311,versionNumber:35,alignmentPatternCenters:[6,30,54,78,102,126,150],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:121},{numBlocks:7,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:47},{numBlocks:26,dataCodewordsPerBlock:48}]},
|
||||||
|
{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:39,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:41,dataCodewordsPerBlock:16}]}]},{infoBits:150283,versionNumber:36,alignmentPatternCenters:[6,24,50,76,102,128,154],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:121},{numBlocks:14,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,
|
||||||
|
dataCodewordsPerBlock:47},{numBlocks:34,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:46,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:64,dataCodewordsPerBlock:16}]}]},{infoBits:152622,versionNumber:37,alignmentPatternCenters:[6,28,54,80,106,132,158],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},
|
||||||
|
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:46},{numBlocks:14,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:49,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:24,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:158308,versionNumber:38,alignmentPatternCenters:[6,32,58,84,110,136,162],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,
|
||||||
|
dataCodewordsPerBlock:122},{numBlocks:18,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:46},{numBlocks:32,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:48,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:15},{numBlocks:32,dataCodewordsPerBlock:16}]}]},{infoBits:161089,versionNumber:39,alignmentPatternCenters:[6,26,54,82,110,138,166],
|
||||||
|
errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:40,dataCodewordsPerBlock:47},{numBlocks:7,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:43,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:15},{numBlocks:67,dataCodewordsPerBlock:16}]}]},{infoBits:167017,
|
||||||
|
versionNumber:40,alignmentPatternCenters:[6,30,58,86,114,142,170],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:118},{numBlocks:6,dataCodewordsPerBlock:119}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:18,dataCodewordsPerBlock:47},{numBlocks:31,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:24},{numBlocks:34,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:15},
|
||||||
|
{numBlocks:61,dataCodewordsPerBlock:16}]}]}];function J(a,b){a^=b;for(b=0;a;)b++,a&=a-1;return b}function K(a,b){return b<<1|a}
|
||||||
|
let ia=[{bits:21522,formatInfo:{errorCorrectionLevel:1,dataMask:0}},{bits:20773,formatInfo:{errorCorrectionLevel:1,dataMask:1}},{bits:24188,formatInfo:{errorCorrectionLevel:1,dataMask:2}},{bits:23371,formatInfo:{errorCorrectionLevel:1,dataMask:3}},{bits:17913,formatInfo:{errorCorrectionLevel:1,dataMask:4}},{bits:16590,formatInfo:{errorCorrectionLevel:1,dataMask:5}},{bits:20375,formatInfo:{errorCorrectionLevel:1,dataMask:6}},{bits:19104,formatInfo:{errorCorrectionLevel:1,dataMask:7}},{bits:30660,formatInfo:{errorCorrectionLevel:0,
|
||||||
|
dataMask:0}},{bits:29427,formatInfo:{errorCorrectionLevel:0,dataMask:1}},{bits:32170,formatInfo:{errorCorrectionLevel:0,dataMask:2}},{bits:30877,formatInfo:{errorCorrectionLevel:0,dataMask:3}},{bits:26159,formatInfo:{errorCorrectionLevel:0,dataMask:4}},{bits:25368,formatInfo:{errorCorrectionLevel:0,dataMask:5}},{bits:27713,formatInfo:{errorCorrectionLevel:0,dataMask:6}},{bits:26998,formatInfo:{errorCorrectionLevel:0,dataMask:7}},{bits:5769,formatInfo:{errorCorrectionLevel:3,dataMask:0}},{bits:5054,
|
||||||
|
formatInfo:{errorCorrectionLevel:3,dataMask:1}},{bits:7399,formatInfo:{errorCorrectionLevel:3,dataMask:2}},{bits:6608,formatInfo:{errorCorrectionLevel:3,dataMask:3}},{bits:1890,formatInfo:{errorCorrectionLevel:3,dataMask:4}},{bits:597,formatInfo:{errorCorrectionLevel:3,dataMask:5}},{bits:3340,formatInfo:{errorCorrectionLevel:3,dataMask:6}},{bits:2107,formatInfo:{errorCorrectionLevel:3,dataMask:7}},{bits:13663,formatInfo:{errorCorrectionLevel:2,dataMask:0}},{bits:12392,formatInfo:{errorCorrectionLevel:2,
|
||||||
|
dataMask:1}},{bits:16177,formatInfo:{errorCorrectionLevel:2,dataMask:2}},{bits:14854,formatInfo:{errorCorrectionLevel:2,dataMask:3}},{bits:9396,formatInfo:{errorCorrectionLevel:2,dataMask:4}},{bits:8579,formatInfo:{errorCorrectionLevel:2,dataMask:5}},{bits:11994,formatInfo:{errorCorrectionLevel:2,dataMask:6}},{bits:11245,formatInfo:{errorCorrectionLevel:2,dataMask:7}}],ja=[a=>0===(a.y+a.x)%2,a=>0===a.y%2,a=>0===a.x%3,a=>0===(a.y+a.x)%3,a=>0===(Math.floor(a.y/2)+Math.floor(a.x/3))%2,a=>0===a.x*a.y%
|
||||||
|
2+a.x*a.y%3,a=>0===(a.y*a.x%2+a.y*a.x%3)%2,a=>0===((a.y+a.x)%2+a.y*a.x%3)%2];
|
||||||
|
function ka(a,b,c){c=ja[c.dataMask];let d=a.height;var e=17+4*b.versionNumber;let f=x.createEmpty(e,e);f.setRegion(0,0,9,9,!0);f.setRegion(e-8,0,8,9,!0);f.setRegion(0,e-8,9,8,!0);for(var g of b.alignmentPatternCenters)for(var h of b.alignmentPatternCenters)6===g&&6===h||6===g&&h===e-7||g===e-7&&6===h||f.setRegion(g-2,h-2,5,5,!0);f.setRegion(6,9,1,e-17,!0);f.setRegion(9,6,e-17,1,!0);6<b.versionNumber&&(f.setRegion(e-11,0,3,6,!0),f.setRegion(0,e-11,6,3,!0));b=[];h=g=0;e=!0;for(let k=d-1;0<k;k-=2){6===
|
||||||
|
k&&k--;for(let m=0;m<d;m++){let l=e?d-1-m:m;for(let n=0;2>n;n++){let q=k-n;if(!f.get(q,l)){h++;let r=a.get(q,l);c({y:l,x:q})&&(r=!r);g=g<<1|r;8===h&&(b.push(g),g=h=0)}}}e=!e}return b}
|
||||||
|
function la(a){var b=a.height,c=Math.floor((b-17)/4);if(6>=c)return I[c-1];c=0;for(var d=5;0<=d;d--)for(var e=b-9;e>=b-11;e--)c=K(a.get(e,d),c);d=0;for(e=5;0<=e;e--)for(let g=b-9;g>=b-11;g--)d=K(a.get(e,g),d);a=Infinity;let f;for(let g of I){if(g.infoBits===c||g.infoBits===d)return g;b=J(c,g.infoBits);b<a&&(f=g,a=b);b=J(d,g.infoBits);b<a&&(f=g,a=b)}if(3>=a)return f}
|
||||||
|
function ma(a){let b=0;for(var c=0;8>=c;c++)6!==c&&(b=K(a.get(c,8),b));for(c=7;0<=c;c--)6!==c&&(b=K(a.get(8,c),b));var d=a.height;c=0;for(var e=d-1;e>=d-7;e--)c=K(a.get(8,e),c);for(e=d-8;e<d;e++)c=K(a.get(e,8),c);a=Infinity;d=null;for(let {bits:f,formatInfo:g}of ia){if(f===b||f===c)return g;e=J(b,f);e<a&&(d=g,a=e);b!==c&&(e=J(c,f),e<a&&(d=g,a=e))}return 3>=a?d:null}
|
||||||
|
function na(a,b,c){let d=b.errorCorrectionLevels[c],e=[],f=0;d.ecBlocks.forEach(h=>{for(let k=0;k<h.numBlocks;k++)e.push({numDataCodewords:h.dataCodewordsPerBlock,codewords:[]}),f+=h.dataCodewordsPerBlock+d.ecCodewordsPerBlock});if(a.length<f)return null;a=a.slice(0,f);b=d.ecBlocks[0].dataCodewordsPerBlock;for(c=0;c<b;c++)for(var g of e)g.codewords.push(a.shift());if(1<d.ecBlocks.length)for(g=d.ecBlocks[0].numBlocks,b=d.ecBlocks[1].numBlocks,c=0;c<b;c++)e[g+c].codewords.push(a.shift());for(;0<a.length;)for(let h of e)h.codewords.push(a.shift());
|
||||||
|
return e}function L(a){let b=la(a);if(!b)return null;var c=ma(a);if(!c)return null;a=ka(a,b,c);var d=na(a,b,c.errorCorrectionLevel);if(!d)return null;c=d.reduce((e,f)=>e+f.numDataCodewords,0);c=new Uint8ClampedArray(c);a=0;for(let e of d){d=ha(e.codewords,e.codewords.length-e.numDataCodewords);if(!d)return null;for(let f=0;f<e.numDataCodewords;f++)c[a++]=d[f]}try{return da(c,b.versionNumber)}catch(e){return null}}
|
||||||
|
function M(a,b,c,d){var e=a.x-b.x+c.x-d.x;let f=a.y-b.y+c.y-d.y;if(0===e&&0===f)return{a11:b.x-a.x,a12:b.y-a.y,a13:0,a21:c.x-b.x,a22:c.y-b.y,a23:0,a31:a.x,a32:a.y,a33:1};let g=b.x-c.x;var h=d.x-c.x;let k=b.y-c.y,m=d.y-c.y;c=g*m-h*k;h=(e*m-h*f)/c;e=(g*f-e*k)/c;return{a11:b.x-a.x+h*b.x,a12:b.y-a.y+h*b.y,a13:h,a21:d.x-a.x+e*d.x,a22:d.y-a.y+e*d.y,a23:e,a31:a.x,a32:a.y,a33:1}}
|
||||||
|
function oa(a,b,c,d){a=M(a,b,c,d);return{a11:a.a22*a.a33-a.a23*a.a32,a12:a.a13*a.a32-a.a12*a.a33,a13:a.a12*a.a23-a.a13*a.a22,a21:a.a23*a.a31-a.a21*a.a33,a22:a.a11*a.a33-a.a13*a.a31,a23:a.a13*a.a21-a.a11*a.a23,a31:a.a21*a.a32-a.a22*a.a31,a32:a.a12*a.a31-a.a11*a.a32,a33:a.a11*a.a22-a.a12*a.a21}}
|
||||||
|
function pa(a,b){var c=oa({x:3.5,y:3.5},{x:b.dimension-3.5,y:3.5},{x:b.dimension-6.5,y:b.dimension-6.5},{x:3.5,y:b.dimension-3.5}),d=M(b.topLeft,b.topRight,b.alignmentPattern,b.bottomLeft),e=d.a11*c.a11+d.a21*c.a12+d.a31*c.a13,f=d.a12*c.a11+d.a22*c.a12+d.a32*c.a13,g=d.a13*c.a11+d.a23*c.a12+d.a33*c.a13,h=d.a11*c.a21+d.a21*c.a22+d.a31*c.a23,k=d.a12*c.a21+d.a22*c.a22+d.a32*c.a23,m=d.a13*c.a21+d.a23*c.a22+d.a33*c.a23,l=d.a11*c.a31+d.a21*c.a32+d.a31*c.a33,n=d.a12*c.a31+d.a22*c.a32+d.a32*c.a33,q=d.a13*
|
||||||
|
c.a31+d.a23*c.a32+d.a33*c.a33;c=x.createEmpty(b.dimension,b.dimension);d=(r,u)=>{const p=g*r+m*u+q;return{x:(e*r+h*u+l)/p,y:(f*r+k*u+n)/p}};for(let r=0;r<b.dimension;r++)for(let u=0;u<b.dimension;u++){let p=d(u+.5,r+.5);c.set(u,r,a.get(Math.floor(p.x),Math.floor(p.y)))}return{matrix:c,mappingFunction:d}}let N=(a,b)=>Math.sqrt(Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2));function O(a){return a.reduce((b,c)=>b+c)}
|
||||||
|
function qa(a,b,c){let d=N(a,b),e=N(b,c),f=N(a,c),g,h,k;e>=d&&e>=f?[g,h,k]=[b,a,c]:f>=e&&f>=d?[g,h,k]=[a,b,c]:[g,h,k]=[a,c,b];0>(k.x-h.x)*(g.y-h.y)-(k.y-h.y)*(g.x-h.x)&&([g,k]=[k,g]);return{bottomLeft:g,topLeft:h,topRight:k}}
|
||||||
|
function ra(a,b,c,d){d=(O(P(a,c,d,5))/7+O(P(a,b,d,5))/7+O(P(c,a,d,5))/7+O(P(b,a,d,5))/7)/4;if(1>d)throw Error("Invalid module size");b=Math.round(N(a,b)/d);a=Math.round(N(a,c)/d);a=Math.floor((b+a)/2)+7;switch(a%4){case 0:a++;break;case 2:a--}return{dimension:a,moduleSize:d}}
|
||||||
|
function Q(a,b,c,d){let e=[{x:Math.floor(a.x),y:Math.floor(a.y)}];var f=Math.abs(b.y-a.y)>Math.abs(b.x-a.x);if(f){var g=Math.floor(a.y);var h=Math.floor(a.x);a=Math.floor(b.y);b=Math.floor(b.x)}else g=Math.floor(a.x),h=Math.floor(a.y),a=Math.floor(b.x),b=Math.floor(b.y);let k=Math.abs(a-g),m=Math.abs(b-h),l=Math.floor(-k/2),n=g<a?1:-1,q=h<b?1:-1,r=!0;for(let u=g,p=h;u!==a+n;u+=n){g=f?p:u;h=f?u:p;if(c.get(g,h)!==r&&(r=!r,e.push({x:g,y:h}),e.length===d+1))break;l+=m;if(0<l){if(p===b)break;p+=q;l-=k}}c=
|
||||||
|
[];for(f=0;f<d;f++)e[f]&&e[f+1]?c.push(N(e[f],e[f+1])):c.push(0);return c}function P(a,b,c,d){let e=b.y-a.y,f=b.x-a.x;b=Q(a,b,c,Math.ceil(d/2));a=Q(a,{x:a.x-f,y:a.y-e},c,Math.ceil(d/2));c=b.shift()+a.shift()-1;return a.concat(c).concat(...b)}function R(a,b){let c=O(a)/O(b),d=0;b.forEach((e,f)=>{d+=Math.pow(a[f]-e*c,2)});return{averageSize:c,error:d}}
|
||||||
|
function S(a,b,c){try{let d=P(a,{x:-1,y:a.y},c,b.length),e=P(a,{x:a.x,y:-1},c,b.length),f=P(a,{x:Math.max(0,a.x-a.y)-1,y:Math.max(0,a.y-a.x)-1},c,b.length),g=P(a,{x:Math.min(c.width,a.x+a.y)+1,y:Math.min(c.height,a.y+a.x)+1},c,b.length),h=R(d,b),k=R(e,b),m=R(f,b),l=R(g,b),n=(h.averageSize+k.averageSize+m.averageSize+l.averageSize)/4;return Math.sqrt(h.error*h.error+k.error*k.error+m.error*m.error+l.error*l.error)+(Math.pow(h.averageSize-n,2)+Math.pow(k.averageSize-n,2)+Math.pow(m.averageSize-n,2)+
|
||||||
|
Math.pow(l.averageSize-n,2))/n}catch(d){return Infinity}}function T(a,b){for(var c=Math.round(b.x);a.get(c,Math.round(b.y));)c--;for(var d=Math.round(b.x);a.get(d,Math.round(b.y));)d++;c=(c+d)/2;for(d=Math.round(b.y);a.get(Math.round(c),d);)d--;for(b=Math.round(b.y);a.get(Math.round(c),b);)b++;return{x:c,y:(d+b)/2}}
|
||||||
|
function sa(a){var b=[],c=[];let d=[];var e=[];for(let p=0;p<=a.height;p++){var f=0,g=!1;let t=[0,0,0,0,0];for(let v=-1;v<=a.width;v++){var h=a.get(v,p);if(h===g)f++;else{t=[t[1],t[2],t[3],t[4],f];f=1;g=h;var k=O(t)/7;k=Math.abs(t[0]-k)<k&&Math.abs(t[1]-k)<k&&Math.abs(t[2]-3*k)<3*k&&Math.abs(t[3]-k)<k&&Math.abs(t[4]-k)<k&&!h;var m=O(t.slice(-3))/3;h=Math.abs(t[2]-m)<m&&Math.abs(t[3]-m)<m&&Math.abs(t[4]-m)<m&&h;if(k){let z=v-t[3]-t[4],y=z-t[2];k={startX:y,endX:z,y:p};m=c.filter(w=>y>=w.bottom.startX&&
|
||||||
|
y<=w.bottom.endX||z>=w.bottom.startX&&y<=w.bottom.endX||y<=w.bottom.startX&&z>=w.bottom.endX&&1.5>t[2]/(w.bottom.endX-w.bottom.startX)&&.5<t[2]/(w.bottom.endX-w.bottom.startX));0<m.length?m[0].bottom=k:c.push({top:k,bottom:k})}if(h){let z=v-t[4],y=z-t[3];h={startX:y,y:p,endX:z};k=e.filter(w=>y>=w.bottom.startX&&y<=w.bottom.endX||z>=w.bottom.startX&&y<=w.bottom.endX||y<=w.bottom.startX&&z>=w.bottom.endX&&1.5>t[2]/(w.bottom.endX-w.bottom.startX)&&.5<t[2]/(w.bottom.endX-w.bottom.startX));0<k.length?
|
||||||
|
k[0].bottom=h:e.push({top:h,bottom:h})}}}b.push(...c.filter(v=>v.bottom.y!==p&&2<=v.bottom.y-v.top.y));c=c.filter(v=>v.bottom.y===p);d.push(...e.filter(v=>v.bottom.y!==p));e=e.filter(v=>v.bottom.y===p)}b.push(...c.filter(p=>2<=p.bottom.y-p.top.y));d.push(...e);c=[];for(var l of b)2>l.bottom.y-l.top.y||(b=(l.top.startX+l.top.endX+l.bottom.startX+l.bottom.endX)/4,e=(l.top.y+l.bottom.y+1)/2,a.get(Math.round(b),Math.round(e))&&(f=[l.top.endX-l.top.startX,l.bottom.endX-l.bottom.startX,l.bottom.y-l.top.y+
|
||||||
|
1],f=O(f)/f.length,g=S({x:Math.round(b),y:Math.round(e)},[1,1,3,1,1],a),c.push({score:g,x:b,y:e,size:f})));if(3>c.length)return null;c.sort((p,t)=>p.score-t.score);l=[];for(b=0;b<Math.min(c.length,5);++b){e=c[b];f=[];for(var n of c)n!==e&&f.push(Object.assign(Object.assign({},n),{score:n.score+Math.pow(n.size-e.size,2)/e.size}));f.sort((p,t)=>p.score-t.score);l.push({points:[e,f[0],f[1]],score:e.score+f[0].score+f[1].score})}l.sort((p,t)=>p.score-t.score);let {topRight:q,topLeft:r,bottomLeft:u}=qa(...l[0].points);
|
||||||
|
l=U(a,d,q,r,u);n=[];l&&n.push({alignmentPattern:{x:l.alignmentPattern.x,y:l.alignmentPattern.y},bottomLeft:{x:u.x,y:u.y},dimension:l.dimension,topLeft:{x:r.x,y:r.y},topRight:{x:q.x,y:q.y}});l=T(a,q);b=T(a,r);c=T(a,u);(a=U(a,d,l,b,c))&&n.push({alignmentPattern:{x:a.alignmentPattern.x,y:a.alignmentPattern.y},bottomLeft:{x:c.x,y:c.y},topLeft:{x:b.x,y:b.y},topRight:{x:l.x,y:l.y},dimension:a.dimension});return 0===n.length?null:n}
|
||||||
|
function U(a,b,c,d,e){let f,g;try{({dimension:f,moduleSize:g}=ra(d,c,e,a))}catch(l){return null}var h=c.x-d.x+e.x,k=c.y-d.y+e.y;c=(N(d,e)+N(d,c))/2/g;e=1-3/c;let m={x:d.x+e*(h-d.x),y:d.y+e*(k-d.y)};b=b.map(l=>{const n=(l.top.startX+l.top.endX+l.bottom.startX+l.bottom.endX)/4;l=(l.top.y+l.bottom.y+1)/2;if(a.get(Math.floor(n),Math.floor(l))){var q=S({x:Math.floor(n),y:Math.floor(l)},[1,1,1],a)+N({x:n,y:l},m);return{x:n,y:l,score:q}}}).filter(l=>!!l).sort((l,n)=>l.score-n.score);return{alignmentPattern:15<=
|
||||||
|
c&&b.length?b[0]:m,dimension:f}}
|
||||||
|
function V(a){var b=sa(a);if(!b)return null;for(let e of b){b=pa(a,e);var c=b.matrix;if(null==c)c=null;else{var d=L(c);if(d)c=d;else{for(d=0;d<c.width;d++)for(let f=d+1;f<c.height;f++)c.get(d,f)!==c.get(f,d)&&(c.set(d,f,!c.get(d,f)),c.set(f,d,!c.get(f,d)));c=L(c)}}if(c)return{binaryData:c.bytes,data:c.text,chunks:c.chunks,version:c.version,location:{topRightCorner:b.mappingFunction(e.dimension,0),topLeftCorner:b.mappingFunction(0,0),bottomRightCorner:b.mappingFunction(e.dimension,e.dimension),bottomLeftCorner:b.mappingFunction(0,
|
||||||
|
e.dimension),topRightFinderPattern:e.topRight,topLeftFinderPattern:e.topLeft,bottomLeftFinderPattern:e.bottomLeft,bottomRightAlignmentPattern:e.alignmentPattern},matrix:b.matrix}}return null}let ta={inversionAttempts:"attemptBoth",greyScaleWeights:{red:.2126,green:.7152,blue:.0722,useIntegerApproximation:!1},canOverwriteImage:!0};function W(a,b){Object.keys(b).forEach(c=>{a[c]=b[c]})}
|
||||||
|
function X(a,b,c,d={}){let e=Object.create(null);W(e,ta);W(e,d);d="onlyInvert"===e.inversionAttempts||"invertFirst"===e.inversionAttempts;var f="attemptBoth"===e.inversionAttempts||d;var g=e.greyScaleWeights,h=e.canOverwriteImage,k=b*c;if(a.length!==4*k)throw Error("Malformed data passed to binarizer.");var m=0;if(h){var l=new Uint8ClampedArray(a.buffer,m,k);m+=k}l=new A(b,c,l);if(g.useIntegerApproximation)for(var n=0;n<c;n++)for(var q=0;q<b;q++){var r=4*(n*b+q);l.set(q,n,g.red*a[r]+g.green*a[r+1]+
|
||||||
|
g.blue*a[r+2]+128>>8)}else for(n=0;n<c;n++)for(q=0;q<b;q++)r=4*(n*b+q),l.set(q,n,g.red*a[r]+g.green*a[r+1]+g.blue*a[r+2]);g=Math.ceil(b/8);n=Math.ceil(c/8);q=g*n;if(h){var u=new Uint8ClampedArray(a.buffer,m,q);m+=q}u=new A(g,n,u);for(q=0;q<n;q++)for(r=0;r<g;r++){var p=Infinity,t=0;for(var v=0;8>v;v++)for(let w=0;8>w;w++){let aa=l.get(8*r+w,8*q+v);p=Math.min(p,aa);t=Math.max(t,aa)}v=(p+t)/2;v=Math.min(255,1.11*v);24>=t-p&&(v=p/2,0<q&&0<r&&(t=(u.get(r,q-1)+2*u.get(r-1,q)+u.get(r-1,q-1))/4,p<t&&(v=t)));
|
||||||
|
u.set(r,q,v)}h?(q=new Uint8ClampedArray(a.buffer,m,k),m+=k,q=new x(q,b)):q=x.createEmpty(b,c);r=null;f&&(h?(a=new Uint8ClampedArray(a.buffer,m,k),r=new x(a,b)):r=x.createEmpty(b,c));for(b=0;b<n;b++)for(a=0;a<g;a++){c=g-3;c=2>a?2:a>c?c:a;h=n-3;h=2>b?2:b>h?h:b;k=0;for(m=-2;2>=m;m++)for(p=-2;2>=p;p++)k+=u.get(c+m,h+p);c=k/25;for(h=0;8>h;h++)for(k=0;8>k;k++)m=8*a+h,p=8*b+k,t=l.get(m,p),q.set(m,p,t<=c),f&&r.set(m,p,!(t<=c))}f=f?{binarized:q,inverted:r}:{binarized:q};let {binarized:z,inverted:y}=f;(f=V(d?
|
||||||
|
y:z))||"attemptBoth"!==e.inversionAttempts&&"invertFirst"!==e.inversionAttempts||(f=V(d?z:y));return f}X.default=X;let Y="dontInvert",Z={red:77,green:150,blue:29,useIntegerApproximation:!0};
|
||||||
|
self.onmessage=a=>{let b=a.data.id,c=a.data.data;switch(a.data.type){case "decode":(a=X(c.data,c.width,c.height,{inversionAttempts:Y,greyScaleWeights:Z}))?self.postMessage({id:b,type:"qrResult",data:a.data,cornerPoints:[a.location.topLeftCorner,a.location.topRightCorner,a.location.bottomRightCorner,a.location.bottomLeftCorner]}):self.postMessage({id:b,type:"qrResult",data:null});break;case "grayscaleWeights":Z.red=c.red;Z.green=c.green;Z.blue=c.blue;Z.useIntegerApproximation=c.useIntegerApproximation;
|
||||||
|
break;case "inversionMode":switch(c){case "original":Y="dontInvert";break;case "invert":Y="onlyInvert";break;case "both":Y="attemptBoth";break;default:throw Error("Invalid inversion mode");}break;case "close":self.close()}}
|
||||||
|
`]),{type:"application/javascript"}))//# sourceMappingURL=qr-scanner-worker.min.js.map
|
32
InvenTree/InvenTree/static/script/qr-scanner.umd.min.js
vendored
Normal file
32
InvenTree/InvenTree/static/script/qr-scanner.umd.min.js
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*! qr-scanner v1.4.1 https://github.com/nimiq/qr-scanner Licensed MIT */
|
||||||
|
'use strict';(function(e,a){"object"===typeof exports&&"undefined"!==typeof module?module.exports=a():"function"===typeof define&&define.amd?define(a):(e="undefined"!==typeof globalThis?globalThis:e||self,e.QrScanner=a())})(this,function(){class e{constructor(a,b,c,d,f){this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._preferredCamera="environment";this._maxScansPerSecond=25;this._lastScanTimestamp=-1;this._destroyed=this._flashOn=this._paused=this._active=!1;this.$video=a;this.$canvas=document.createElement("canvas");
|
||||||
|
c&&"object"===typeof c?this._onDecode=b:(c||d||f?console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future"):console.warn("Note that the type of the scan result passed to onDecode will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."),this._legacyOnDecode=b);b="object"===typeof c?c:{};this._onDecodeError=b.onDecodeError||("function"===typeof c?c:this._onDecodeError);this._calculateScanRegion=
|
||||||
|
b.calculateScanRegion||("function"===typeof d?d:this._calculateScanRegion);this._preferredCamera=b.preferredCamera||f||this._preferredCamera;this._legacyCanvasSize="number"===typeof c?c:"number"===typeof d?d:this._legacyCanvasSize;this._maxScansPerSecond=b.maxScansPerSecond||this._maxScansPerSecond;this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData=this._onLoadedMetaData.bind(this);this._onVisibilityChange=this._onVisibilityChange.bind(this);this._updateOverlay=this._updateOverlay.bind(this);
|
||||||
|
a.disablePictureInPicture=!0;a.playsInline=!0;a.muted=!0;let h=!1;a.hidden&&(a.hidden=!1,h=!0);document.body.contains(a)||(document.body.appendChild(a),h=!0);c=a.parentElement;if(b.highlightScanRegion||b.highlightCodeOutline){d=!!b.overlay;this.$overlay=b.overlay||document.createElement("div");f=this.$overlay.style;f.position="absolute";f.display="none";f.pointerEvents="none";this.$overlay.classList.add("scan-region-highlight");if(!d&&b.highlightScanRegion){this.$overlay.innerHTML='<svg class="scan-region-highlight-svg" viewBox="0 0 238 238" preserveAspectRatio="none" style="position:absolute;width:100%;height:100%;left:0;top:0;fill:none;stroke:#e9b213;stroke-width:4;stroke-linecap:round;stroke-linejoin:round"><path d="M31 2H10a8 8 0 0 0-8 8v21M207 2h21a8 8 0 0 1 8 8v21m0 176v21a8 8 0 0 1-8 8h-21m-176 0H10a8 8 0 0 1-8-8v-21"/></svg>';
|
||||||
|
try{this.$overlay.firstElementChild.animate({transform:["scale(.98)","scale(1.01)"]},{duration:400,iterations:Infinity,direction:"alternate",easing:"ease-in-out"})}catch(m){}c.insertBefore(this.$overlay,this.$video.nextSibling)}b.highlightCodeOutline&&(this.$overlay.insertAdjacentHTML("beforeend",'<svg class="code-outline-highlight" preserveAspectRatio="none" style="display:none;width:100%;height:100%;fill:none;stroke:#e9b213;stroke-width:5;stroke-dasharray:25;stroke-linecap:round;stroke-linejoin:round"><polygon/></svg>'),
|
||||||
|
this.$codeOutlineHighlight=this.$overlay.lastElementChild)}this._scanRegion=this._calculateScanRegion(a);requestAnimationFrame(()=>{let m=window.getComputedStyle(a);"none"===m.display&&(a.style.setProperty("display","block","important"),h=!0);"visible"!==m.visibility&&(a.style.setProperty("visibility","visible","important"),h=!0);h&&(console.warn("QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."),a.style.opacity="0",a.style.width="0",a.style.height="0",this.$overlay&&
|
||||||
|
this.$overlay.parentElement&&this.$overlay.parentElement.removeChild(this.$overlay),delete this.$overlay,delete this.$codeOutlineHighlight);this.$overlay&&this._updateOverlay()});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);window.addEventListener("resize",this._updateOverlay);this._qrEnginePromise=e.createQrEngine()}static set WORKER_PATH(a){console.warn("Setting QrScanner.WORKER_PATH is not required and not supported anymore. Have a look at the README for new setup instructions.")}static async hasCamera(){try{return!!(await e.listCameras(!1)).length}catch(a){return!1}}static async listCameras(a=
|
||||||
|
!1){if(!navigator.mediaDevices)return[];let b=async()=>(await navigator.mediaDevices.enumerateDevices()).filter(d=>"videoinput"===d.kind),c;try{a&&(await b()).every(d=>!d.label)&&(c=await navigator.mediaDevices.getUserMedia({audio:!1,video:!0}))}catch(d){}try{return(await b()).map((d,f)=>({id:d.deviceId,label:d.label||(0===f?"Default Camera":`Camera ${f+1}`)}))}finally{c&&(console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream"),e._stopVideoStream(c))}}async hasFlash(){let a;
|
||||||
|
try{if(this.$video.srcObject){if(!(this.$video.srcObject instanceof MediaStream))return!1;a=this.$video.srcObject}else a=(await this._getCameraStream()).stream;return"torch"in a.getVideoTracks()[0].getSettings()}catch(b){return!1}finally{a&&a!==this.$video.srcObject&&(console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream"),e._stopVideoStream(a))}}isFlashOn(){return this._flashOn}async toggleFlash(){this._flashOn?await this.turnFlashOff():await this.turnFlashOn()}async turnFlashOn(){if(!this._flashOn&&
|
||||||
|
!this._destroyed&&(this._flashOn=!0,this._active&&!this._paused))try{if(!await this.hasFlash())throw"No flash available";await this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:!0}]})}catch(a){throw this._flashOn=!1,a;}}async turnFlashOff(){this._flashOn&&(this._flashOn=!1,await this._restartVideoStream())}destroy(){this.$video.removeEventListener("loadedmetadata",this._onLoadedMetaData);this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",
|
||||||
|
this._onVisibilityChange);window.removeEventListener("resize",this._updateOverlay);this._destroyed=!0;this._flashOn=!1;this.stop();e._postWorkerMessage(this._qrEnginePromise,"close")}async start(){if(this._destroyed)throw Error("The QR scanner can not be started as it had been destroyed.");if(!this._active||this._paused)if("https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https."),this._active=!0,!document.hidden)if(this._paused=
|
||||||
|
!1,this.$video.srcObject)await this.$video.play();else try{let {stream:a,facingMode:b}=await this._getCameraStream();!this._active||this._paused?e._stopVideoStream(a):(this._setVideoMirror(b),this.$video.srcObject=a,await this.$video.play(),this._flashOn&&(this._flashOn=!1,this.turnFlashOn().catch(()=>{})))}catch(a){if(!this._paused)throw this._active=!1,a;}}stop(){this.pause();this._active=!1}async pause(a=!1){this._paused=!0;if(!this._active)return!0;this.$video.pause();this.$overlay&&(this.$overlay.style.display=
|
||||||
|
"none");let b=()=>{this.$video.srcObject instanceof MediaStream&&(e._stopVideoStream(this.$video.srcObject),this.$video.srcObject=null)};if(a)return b(),!0;await new Promise(c=>setTimeout(c,300));if(!this._paused)return!1;b();return!0}async setCamera(a){a!==this._preferredCamera&&(this._preferredCamera=a,await this._restartVideoStream())}static async scanImage(a,b,c,d,f=!1,h=!1){let m,n=!1;b&&("scanRegion"in b||"qrEngine"in b||"canvas"in b||"disallowCanvasResizing"in b||"alsoTryWithoutScanRegion"in
|
||||||
|
b||"returnDetailedScanResult"in b)?(m=b.scanRegion,c=b.qrEngine,d=b.canvas,f=b.disallowCanvasResizing||!1,h=b.alsoTryWithoutScanRegion||!1,n=!0):b||c||d||f||h?console.warn("You're using a deprecated api for scanImage which will be removed in the future."):console.warn("Note that the return type of scanImage will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true.");b=!!c;try{let p,k;[c,p]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);
|
||||||
|
[d,k]=e._drawToCanvas(p,m,d,f);let q;if(c instanceof Worker){let g=c;b||e._postWorkerMessageSync(g,"inversionMode","both");q=await new Promise((l,v)=>{let w,u,r,y=-1;u=t=>{t.data.id===y&&(g.removeEventListener("message",u),g.removeEventListener("error",r),clearTimeout(w),null!==t.data.data?l({data:t.data.data,cornerPoints:e._convertPoints(t.data.cornerPoints,m)}):v(e.NO_QR_CODE_FOUND))};r=t=>{g.removeEventListener("message",u);g.removeEventListener("error",r);clearTimeout(w);v("Scanner error: "+(t?
|
||||||
|
t.message||t:"Unknown Error"))};g.addEventListener("message",u);g.addEventListener("error",r);w=setTimeout(()=>r("timeout"),1E4);let x=k.getImageData(0,0,d.width,d.height);y=e._postWorkerMessageSync(g,"decode",x,[x.data.buffer])})}else q=await Promise.race([new Promise((g,l)=>window.setTimeout(()=>l("Scanner error: timeout"),1E4)),(async()=>{try{var [g]=await c.detect(d);if(!g)throw e.NO_QR_CODE_FOUND;return{data:g.rawValue,cornerPoints:e._convertPoints(g.cornerPoints,m)}}catch(l){g=l.message||l;
|
||||||
|
if(/not implemented|service unavailable/.test(g))return e._disableBarcodeDetector=!0,e.scanImage(a,{scanRegion:m,canvas:d,disallowCanvasResizing:f,alsoTryWithoutScanRegion:h});throw`Scanner error: ${g}`;}})()]);return n?q:q.data}catch(p){if(!m||!h)throw p;let k=await e.scanImage(a,{qrEngine:c,canvas:d,disallowCanvasResizing:f});return n?k:k.data}finally{b||e._postWorkerMessage(c,"close")}}setGrayscaleWeights(a,b,c,d=!0){e._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,
|
||||||
|
blue:c,useIntegerApproximation:d})}setInversionMode(a){e._postWorkerMessage(this._qrEnginePromise,"inversionMode",a)}static async createQrEngine(a){a&&console.warn("Specifying a worker path is not required and not supported anymore.");return!e._disableBarcodeDetector&&"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(await BarcodeDetector.getSupportedFormats()).includes("qr_code")?new BarcodeDetector({formats:["qr_code"]}):import("./qr-scanner-worker.min.js").then(b=>b.createWorker())}_onPlay(){this._scanRegion=
|
||||||
|
this._calculateScanRegion(this.$video);this._updateOverlay();this.$overlay&&(this.$overlay.style.display="");this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video);this._updateOverlay()}_onVisibilityChange(){document.hidden?this.pause():this._active&&this.start()}_calculateScanRegion(a){let b=Math.round(2/3*Math.min(a.videoWidth,a.videoHeight));return{x:Math.round((a.videoWidth-b)/2),y:Math.round((a.videoHeight-b)/2),width:b,height:b,downScaledWidth:this._legacyCanvasSize,
|
||||||
|
downScaledHeight:this._legacyCanvasSize}}_updateOverlay(){requestAnimationFrame(()=>{if(this.$overlay){var a=this.$video,b=a.videoWidth,c=a.videoHeight,d=a.offsetWidth,f=a.offsetHeight,h=a.offsetLeft,m=a.offsetTop,n=window.getComputedStyle(a),p=n.objectFit,k=b/c,q=d/f;switch(p){case "none":var g=b;var l=c;break;case "fill":g=d;l=f;break;default:("cover"===p?k>q:k<q)?(l=f,g=l*k):(g=d,l=g/k),"scale-down"===p&&(g=Math.min(g,b),l=Math.min(l,c))}var [v,w]=n.objectPosition.split(" ").map((r,y)=>{const x=
|
||||||
|
parseFloat(r);return r.endsWith("%")?(y?f-l:d-g)*x/100:x});n=this._scanRegion.width||b;q=this._scanRegion.height||c;p=this._scanRegion.x||0;var u=this._scanRegion.y||0;k=this.$overlay.style;k.width=`${n/b*g}px`;k.height=`${q/c*l}px`;k.top=`${m+w+u/c*l}px`;c=/scaleX\(-1\)/.test(a.style.transform);k.left=`${h+(c?d-v-g:v)+(c?b-p-n:p)/b*g}px`;k.transform=a.style.transform}})}static _convertPoints(a,b){if(!b)return a;let c=b.x||0,d=b.y||0,f=b.width&&b.downScaledWidth?b.width/b.downScaledWidth:1;b=b.height&&
|
||||||
|
b.downScaledHeight?b.height/b.downScaledHeight:1;for(let h of a)h.x=h.x*f+c,h.y=h.y*b+d;return a}_scanFrame(){!this._active||this.$video.paused||this.$video.ended||("requestVideoFrameCallback"in this.$video?this.$video.requestVideoFrameCallback.bind(this.$video):requestAnimationFrame)(async()=>{if(!(1>=this.$video.readyState)){var a=Date.now()-this._lastScanTimestamp,b=1E3/this._maxScansPerSecond;a<b&&await new Promise(d=>setTimeout(d,b-a));this._lastScanTimestamp=Date.now();try{var c=await e.scanImage(this.$video,
|
||||||
|
{scanRegion:this._scanRegion,qrEngine:this._qrEnginePromise,canvas:this.$canvas})}catch(d){if(!this._active)return;this._onDecodeError(d)}!e._disableBarcodeDetector||await this._qrEnginePromise instanceof Worker||(this._qrEnginePromise=e.createQrEngine());c?(this._onDecode?this._onDecode(c):this._legacyOnDecode&&this._legacyOnDecode(c.data),this.$codeOutlineHighlight&&(clearTimeout(this._codeOutlineHighlightRemovalTimeout),this._codeOutlineHighlightRemovalTimeout=void 0,this.$codeOutlineHighlight.setAttribute("viewBox",
|
||||||
|
`${this._scanRegion.x||0} `+`${this._scanRegion.y||0} `+`${this._scanRegion.width||this.$video.videoWidth} `+`${this._scanRegion.height||this.$video.videoHeight}`),this.$codeOutlineHighlight.firstElementChild.setAttribute("points",c.cornerPoints.map(({x:d,y:f})=>`${d},${f}`).join(" ")),this.$codeOutlineHighlight.style.display="")):this.$codeOutlineHighlight&&!this._codeOutlineHighlightRemovalTimeout&&(this._codeOutlineHighlightRemovalTimeout=setTimeout(()=>this.$codeOutlineHighlight.style.display=
|
||||||
|
"none",100))}this._scanFrame()})}_onDecodeError(a){a!==e.NO_QR_CODE_FOUND&&console.log(a)}async _getCameraStream(){if(!navigator.mediaDevices)throw"Camera not found.";let a=/^(environment|user)$/.test(this._preferredCamera)?"facingMode":"deviceId",b=[{width:{min:1024}},{width:{min:768}},{}],c=b.map(d=>Object.assign({},d,{[a]:{exact:this._preferredCamera}}));for(let d of[...c,...b])try{let f=await navigator.mediaDevices.getUserMedia({video:d,audio:!1}),h=this._getFacingMode(f)||(d.facingMode?this._preferredCamera:
|
||||||
|
"environment"===this._preferredCamera?"user":"environment");return{stream:f,facingMode:h}}catch(f){}throw"Camera not found.";}async _restartVideoStream(){let a=this._paused;await this.pause(!0)&&!a&&this._active&&await this.start()}static _stopVideoStream(a){for(let b of a.getTracks())b.stop(),a.removeTrack(b)}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}_getFacingMode(a){return(a=a.getVideoTracks()[0])?/rear|back|environment/i.test(a.label)?"environment":/front|user|face/i.test(a.label)?
|
||||||
|
"user":null:null}static _drawToCanvas(a,b,c,d=!1){c=c||document.createElement("canvas");let f=b&&b.x?b.x:0,h=b&&b.y?b.y:0,m=b&&b.width?b.width:a.videoWidth||a.width,n=b&&b.height?b.height:a.videoHeight||a.height;d||(d=b&&b.downScaledWidth?b.downScaledWidth:m,b=b&&b.downScaledHeight?b.downScaledHeight:n,c.width!==d&&(c.width=d),c.height!==b&&(c.height=b));b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,f,h,m,n,0,0,c.width,c.height);return[c,b]}static async _loadImage(a){if(a instanceof
|
||||||
|
Image)return await e._awaitImageLoad(a),a;if(a instanceof HTMLVideoElement||a instanceof HTMLCanvasElement||a instanceof SVGImageElement||"OffscreenCanvas"in window&&a instanceof OffscreenCanvas||"ImageBitmap"in window&&a instanceof ImageBitmap)return a;if(a instanceof File||a instanceof Blob||a instanceof URL||"string"===typeof a){let b=new Image;b.src=a instanceof File||a instanceof Blob?URL.createObjectURL(a):a.toString();try{return await e._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof
|
||||||
|
Blob)&&URL.revokeObjectURL(b.src)}}else throw"Unsupported image type.";}static async _awaitImageLoad(a){a.complete&&0!==a.naturalWidth||await new Promise((b,c)=>{let d=f=>{a.removeEventListener("load",d);a.removeEventListener("error",d);f instanceof ErrorEvent?c("Image load error"):b()};a.addEventListener("load",d);a.addEventListener("error",d)})}static async _postWorkerMessage(a,b,c,d){return e._postWorkerMessageSync(await a,b,c,d)}static _postWorkerMessageSync(a,b,c,d){if(!(a instanceof Worker))return-1;
|
||||||
|
let f=e._workerMessageId++;a.postMessage({id:f,type:b,data:c},d);return f}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e._disableBarcodeDetector=!1;e._workerMessageId=0;return e})
|
||||||
|
//# sourceMappingURL=qr-scanner.umd.min.js.map
|
@ -72,7 +72,7 @@ class ViewTests(TestCase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Change this number as more javascript files are added to the index page
|
# Change this number as more javascript files are added to the index page
|
||||||
N_SCRIPT_FILES = 39
|
N_SCRIPT_FILES = 40
|
||||||
|
|
||||||
content = self.get_index_page()
|
content = self.get_index_page()
|
||||||
|
|
||||||
|
@ -637,7 +637,7 @@ class SupplierPart(models.Model):
|
|||||||
get_price = common.models.get_price
|
get_price = common.models.get_price
|
||||||
|
|
||||||
def open_orders(self):
|
def open_orders(self):
|
||||||
""" Return a database query for PO line items for this SupplierPart,
|
""" Return a database query for PurchaseOrder line items for this SupplierPart,
|
||||||
limited to purchase orders that are open / outstanding.
|
limited to purchase orders that are open / outstanding.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -8,11 +8,35 @@ from import_export.admin import ImportExportModelAdmin
|
|||||||
from import_export.resources import ModelResource
|
from import_export.resources import ModelResource
|
||||||
from import_export.fields import Field
|
from import_export.fields import Field
|
||||||
|
|
||||||
from .models import PurchaseOrder, PurchaseOrderLineItem
|
from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderExtraLine
|
||||||
from .models import SalesOrder, SalesOrderLineItem
|
from .models import SalesOrder, SalesOrderLineItem, SalesOrderExtraLine
|
||||||
from .models import SalesOrderShipment, SalesOrderAllocation
|
from .models import SalesOrderShipment, SalesOrderAllocation
|
||||||
|
|
||||||
|
|
||||||
|
# region general classes
|
||||||
|
class GeneralExtraLineAdmin:
|
||||||
|
list_display = (
|
||||||
|
'order',
|
||||||
|
'quantity',
|
||||||
|
'reference'
|
||||||
|
)
|
||||||
|
|
||||||
|
search_fields = [
|
||||||
|
'order__reference',
|
||||||
|
'order__customer__name',
|
||||||
|
'reference',
|
||||||
|
]
|
||||||
|
|
||||||
|
autocomplete_fields = ('order', )
|
||||||
|
|
||||||
|
|
||||||
|
class GeneralExtraLineMeta:
|
||||||
|
skip_unchanged = True
|
||||||
|
report_skipped = False
|
||||||
|
clean_model_instances = True
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderLineItemInlineAdmin(admin.StackedInline):
|
class PurchaseOrderLineItemInlineAdmin(admin.StackedInline):
|
||||||
model = PurchaseOrderLineItem
|
model = PurchaseOrderLineItem
|
||||||
extra = 0
|
extra = 0
|
||||||
@ -68,8 +92,8 @@ class SalesOrderAdmin(ImportExportModelAdmin):
|
|||||||
autocomplete_fields = ('customer',)
|
autocomplete_fields = ('customer',)
|
||||||
|
|
||||||
|
|
||||||
class POLineItemResource(ModelResource):
|
class PurchaseOrderLineItemResource(ModelResource):
|
||||||
""" Class for managing import / export of POLineItem data """
|
""" Class for managing import / export of PurchaseOrderLineItem data """
|
||||||
|
|
||||||
part_name = Field(attribute='part__part__name', readonly=True)
|
part_name = Field(attribute='part__part__name', readonly=True)
|
||||||
|
|
||||||
@ -86,9 +110,16 @@ class POLineItemResource(ModelResource):
|
|||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
|
|
||||||
|
|
||||||
class SOLineItemResource(ModelResource):
|
class PurchaseOrderExtraLineResource(ModelResource):
|
||||||
|
""" Class for managing import / export of PurchaseOrderExtraLine data """
|
||||||
|
|
||||||
|
class Meta(GeneralExtraLineMeta):
|
||||||
|
model = PurchaseOrderExtraLine
|
||||||
|
|
||||||
|
|
||||||
|
class SalesOrderLineItemResource(ModelResource):
|
||||||
"""
|
"""
|
||||||
Class for managing import / export of SOLineItem data
|
Class for managing import / export of SalesOrderLineItem data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
part_name = Field(attribute='part__name', readonly=True)
|
part_name = Field(attribute='part__name', readonly=True)
|
||||||
@ -117,9 +148,16 @@ class SOLineItemResource(ModelResource):
|
|||||||
clean_model_instances = True
|
clean_model_instances = True
|
||||||
|
|
||||||
|
|
||||||
|
class SalesOrderExtraLineResource(ModelResource):
|
||||||
|
""" Class for managing import / export of SalesOrderExtraLine data """
|
||||||
|
|
||||||
|
class Meta(GeneralExtraLineMeta):
|
||||||
|
model = SalesOrderExtraLine
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
resource_class = POLineItemResource
|
resource_class = PurchaseOrderLineItemResource
|
||||||
|
|
||||||
list_display = (
|
list_display = (
|
||||||
'order',
|
'order',
|
||||||
@ -133,9 +171,14 @@ class PurchaseOrderLineItemAdmin(ImportExportModelAdmin):
|
|||||||
autocomplete_fields = ('order', 'part', 'destination',)
|
autocomplete_fields = ('order', 'part', 'destination',)
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderExtraLineAdmin(GeneralExtraLineAdmin, ImportExportModelAdmin):
|
||||||
|
|
||||||
|
resource_class = PurchaseOrderExtraLineResource
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderLineItemAdmin(ImportExportModelAdmin):
|
class SalesOrderLineItemAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
resource_class = SOLineItemResource
|
resource_class = SalesOrderLineItemResource
|
||||||
|
|
||||||
list_display = (
|
list_display = (
|
||||||
'order',
|
'order',
|
||||||
@ -154,6 +197,11 @@ class SalesOrderLineItemAdmin(ImportExportModelAdmin):
|
|||||||
autocomplete_fields = ('order', 'part',)
|
autocomplete_fields = ('order', 'part',)
|
||||||
|
|
||||||
|
|
||||||
|
class SalesOrderExtraLineAdmin(GeneralExtraLineAdmin, ImportExportModelAdmin):
|
||||||
|
|
||||||
|
resource_class = SalesOrderExtraLineResource
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderShipmentAdmin(ImportExportModelAdmin):
|
class SalesOrderShipmentAdmin(ImportExportModelAdmin):
|
||||||
|
|
||||||
list_display = [
|
list_display = [
|
||||||
@ -184,9 +232,11 @@ class SalesOrderAllocationAdmin(ImportExportModelAdmin):
|
|||||||
|
|
||||||
admin.site.register(PurchaseOrder, PurchaseOrderAdmin)
|
admin.site.register(PurchaseOrder, PurchaseOrderAdmin)
|
||||||
admin.site.register(PurchaseOrderLineItem, PurchaseOrderLineItemAdmin)
|
admin.site.register(PurchaseOrderLineItem, PurchaseOrderLineItemAdmin)
|
||||||
|
admin.site.register(PurchaseOrderExtraLine, PurchaseOrderExtraLineAdmin)
|
||||||
|
|
||||||
admin.site.register(SalesOrder, SalesOrderAdmin)
|
admin.site.register(SalesOrder, SalesOrderAdmin)
|
||||||
admin.site.register(SalesOrderLineItem, SalesOrderLineItemAdmin)
|
admin.site.register(SalesOrderLineItem, SalesOrderLineItemAdmin)
|
||||||
|
admin.site.register(SalesOrderExtraLine, SalesOrderExtraLineAdmin)
|
||||||
|
|
||||||
admin.site.register(SalesOrderShipment, SalesOrderShipmentAdmin)
|
admin.site.register(SalesOrderShipment, SalesOrderShipmentAdmin)
|
||||||
admin.site.register(SalesOrderAllocation, SalesOrderAllocationAdmin)
|
admin.site.register(SalesOrderAllocation, SalesOrderAllocationAdmin)
|
||||||
|
@ -20,16 +20,68 @@ from InvenTree.helpers import str2bool, DownloadFile
|
|||||||
from InvenTree.api import AttachmentMixin
|
from InvenTree.api import AttachmentMixin
|
||||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
|
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
|
||||||
|
|
||||||
from order.admin import POLineItemResource
|
from order.admin import PurchaseOrderLineItemResource
|
||||||
import order.models as models
|
import order.models as models
|
||||||
import order.serializers as serializers
|
import order.serializers as serializers
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
from users.models import Owner
|
from users.models import Owner
|
||||||
|
|
||||||
|
|
||||||
class POFilter(rest_filters.FilterSet):
|
class GeneralExtraLineList:
|
||||||
"""
|
"""
|
||||||
Custom API filters for the POList endpoint
|
General template for ExtraLine API classes
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
params = self.request.query_params
|
||||||
|
|
||||||
|
kwargs['order_detail'] = str2bool(params.get('order_detail', False))
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
kwargs['context'] = self.get_serializer_context()
|
||||||
|
|
||||||
|
return self.serializer_class(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
|
||||||
|
queryset = super().get_queryset(*args, **kwargs)
|
||||||
|
|
||||||
|
queryset = queryset.prefetch_related(
|
||||||
|
'order',
|
||||||
|
)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
filter_backends = [
|
||||||
|
rest_filters.DjangoFilterBackend,
|
||||||
|
filters.SearchFilter,
|
||||||
|
filters.OrderingFilter
|
||||||
|
]
|
||||||
|
|
||||||
|
ordering_fields = [
|
||||||
|
'title',
|
||||||
|
'quantity',
|
||||||
|
'note',
|
||||||
|
'reference',
|
||||||
|
]
|
||||||
|
|
||||||
|
search_fields = [
|
||||||
|
'title',
|
||||||
|
'quantity',
|
||||||
|
'note',
|
||||||
|
'reference'
|
||||||
|
]
|
||||||
|
|
||||||
|
filter_fields = [
|
||||||
|
'order',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderFilter(rest_filters.FilterSet):
|
||||||
|
"""
|
||||||
|
Custom API filters for the PurchaseOrderList endpoint
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assigned_to_me = rest_filters.BooleanFilter(label='assigned_to_me', method='filter_assigned_to_me')
|
assigned_to_me = rest_filters.BooleanFilter(label='assigned_to_me', method='filter_assigned_to_me')
|
||||||
@ -58,16 +110,16 @@ class POFilter(rest_filters.FilterSet):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class POList(generics.ListCreateAPIView):
|
class PurchaseOrderList(generics.ListCreateAPIView):
|
||||||
""" API endpoint for accessing a list of PurchaseOrder objects
|
""" API endpoint for accessing a list of PurchaseOrder objects
|
||||||
|
|
||||||
- GET: Return list of PO objects (with filters)
|
- GET: Return list of PurchaseOrder objects (with filters)
|
||||||
- POST: Create a new PurchaseOrder object
|
- POST: Create a new PurchaseOrder object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.PurchaseOrder.objects.all()
|
queryset = models.PurchaseOrder.objects.all()
|
||||||
serializer_class = serializers.POSerializer
|
serializer_class = serializers.PurchaseOrderSerializer
|
||||||
filterset_class = POFilter
|
filterset_class = PurchaseOrderFilter
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -104,7 +156,7 @@ class POList(generics.ListCreateAPIView):
|
|||||||
'lines',
|
'lines',
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = serializers.POSerializer.annotate_queryset(queryset)
|
queryset = serializers.PurchaseOrderSerializer.annotate_queryset(queryset)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
@ -202,11 +254,11 @@ class POList(generics.ListCreateAPIView):
|
|||||||
ordering = '-creation_date'
|
ordering = '-creation_date'
|
||||||
|
|
||||||
|
|
||||||
class PODetail(generics.RetrieveUpdateDestroyAPIView):
|
class PurchaseOrderDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
""" API endpoint for detail view of a PurchaseOrder object """
|
""" API endpoint for detail view of a PurchaseOrder object """
|
||||||
|
|
||||||
queryset = models.PurchaseOrder.objects.all()
|
queryset = models.PurchaseOrder.objects.all()
|
||||||
serializer_class = serializers.POSerializer
|
serializer_class = serializers.PurchaseOrderSerializer
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -229,12 +281,12 @@ class PODetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
'lines',
|
'lines',
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = serializers.POSerializer.annotate_queryset(queryset)
|
queryset = serializers.PurchaseOrderSerializer.annotate_queryset(queryset)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class POReceive(generics.CreateAPIView):
|
class PurchaseOrderReceive(generics.CreateAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint to receive stock items against a purchase order.
|
API endpoint to receive stock items against a purchase order.
|
||||||
|
|
||||||
@ -249,7 +301,7 @@ class POReceive(generics.CreateAPIView):
|
|||||||
|
|
||||||
queryset = models.PurchaseOrderLineItem.objects.none()
|
queryset = models.PurchaseOrderLineItem.objects.none()
|
||||||
|
|
||||||
serializer_class = serializers.POReceiveSerializer
|
serializer_class = serializers.PurchaseOrderReceiveSerializer
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
|
|
||||||
@ -266,9 +318,9 @@ class POReceive(generics.CreateAPIView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class POLineItemFilter(rest_filters.FilterSet):
|
class PurchaseOrderLineItemFilter(rest_filters.FilterSet):
|
||||||
"""
|
"""
|
||||||
Custom filters for the POLineItemList endpoint
|
Custom filters for the PurchaseOrderLineItemList endpoint
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -318,22 +370,22 @@ class POLineItemFilter(rest_filters.FilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class POLineItemList(generics.ListCreateAPIView):
|
class PurchaseOrderLineItemList(generics.ListCreateAPIView):
|
||||||
""" API endpoint for accessing a list of POLineItem objects
|
""" API endpoint for accessing a list of PurchaseOrderLineItem objects
|
||||||
|
|
||||||
- GET: Return a list of PO Line Item objects
|
- GET: Return a list of PurchaseOrder Line Item objects
|
||||||
- POST: Create a new PurchaseOrderLineItem object
|
- POST: Create a new PurchaseOrderLineItem object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.PurchaseOrderLineItem.objects.all()
|
queryset = models.PurchaseOrderLineItem.objects.all()
|
||||||
serializer_class = serializers.POLineItemSerializer
|
serializer_class = serializers.PurchaseOrderLineItemSerializer
|
||||||
filterset_class = POLineItemFilter
|
filterset_class = PurchaseOrderLineItemFilter
|
||||||
|
|
||||||
def get_queryset(self, *args, **kwargs):
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
|
||||||
queryset = super().get_queryset(*args, **kwargs)
|
queryset = super().get_queryset(*args, **kwargs)
|
||||||
|
|
||||||
queryset = serializers.POLineItemSerializer.annotate_queryset(queryset)
|
queryset = serializers.PurchaseOrderLineItemSerializer.annotate_queryset(queryset)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
@ -382,7 +434,7 @@ class POLineItemList(generics.ListCreateAPIView):
|
|||||||
export_format = str(export_format).strip().lower()
|
export_format = str(export_format).strip().lower()
|
||||||
|
|
||||||
if export_format in ['csv', 'tsv', 'xls', 'xlsx']:
|
if export_format in ['csv', 'tsv', 'xls', 'xlsx']:
|
||||||
dataset = POLineItemResource().export(queryset=queryset)
|
dataset = PurchaseOrderLineItemResource().export(queryset=queryset)
|
||||||
|
|
||||||
filedata = dataset.export(export_format)
|
filedata = dataset.export(export_format)
|
||||||
|
|
||||||
@ -432,30 +484,46 @@ class POLineItemList(generics.ListCreateAPIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class POLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
class PurchaseOrderLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
Detail API endpoint for PurchaseOrderLineItem object
|
Detail API endpoint for PurchaseOrderLineItem object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.PurchaseOrderLineItem.objects.all()
|
queryset = models.PurchaseOrderLineItem.objects.all()
|
||||||
serializer_class = serializers.POLineItemSerializer
|
serializer_class = serializers.PurchaseOrderLineItemSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
|
|
||||||
queryset = serializers.POLineItemSerializer.annotate_queryset(queryset)
|
queryset = serializers.PurchaseOrderLineItemSerializer.annotate_queryset(queryset)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
class PurchaseOrderExtraLineList(GeneralExtraLineList, generics.ListCreateAPIView):
|
||||||
|
"""
|
||||||
|
API endpoint for accessing a list of PurchaseOrderExtraLine objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = models.PurchaseOrderExtraLine.objects.all()
|
||||||
|
serializer_class = serializers.PurchaseOrderExtraLineSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderExtraLineDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
""" API endpoint for detail view of a PurchaseOrderExtraLine object """
|
||||||
|
|
||||||
|
queryset = models.PurchaseOrderExtraLine.objects.all()
|
||||||
|
serializer_class = serializers.PurchaseOrderExtraLineSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SalesOrderAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||||
"""
|
"""
|
||||||
API endpoint for listing (and creating) a SalesOrderAttachment (file upload)
|
API endpoint for listing (and creating) a SalesOrderAttachment (file upload)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.SalesOrderAttachment.objects.all()
|
queryset = models.SalesOrderAttachment.objects.all()
|
||||||
serializer_class = serializers.SOAttachmentSerializer
|
serializer_class = serializers.SalesOrderAttachmentSerializer
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
rest_filters.DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
@ -466,20 +534,20 @@ class SOAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SOAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
|
class SalesOrderAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
|
||||||
"""
|
"""
|
||||||
Detail endpoint for SalesOrderAttachment
|
Detail endpoint for SalesOrderAttachment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.SalesOrderAttachment.objects.all()
|
queryset = models.SalesOrderAttachment.objects.all()
|
||||||
serializer_class = serializers.SOAttachmentSerializer
|
serializer_class = serializers.SalesOrderAttachmentSerializer
|
||||||
|
|
||||||
|
|
||||||
class SOList(generics.ListCreateAPIView):
|
class SalesOrderList(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for accessing a list of SalesOrder objects.
|
API endpoint for accessing a list of SalesOrder objects.
|
||||||
|
|
||||||
- GET: Return list of SO objects (with filters)
|
- GET: Return list of SalesOrder objects (with filters)
|
||||||
- POST: Create a new SalesOrder
|
- POST: Create a new SalesOrder
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -616,7 +684,7 @@ class SOList(generics.ListCreateAPIView):
|
|||||||
ordering = '-creation_date'
|
ordering = '-creation_date'
|
||||||
|
|
||||||
|
|
||||||
class SODetail(generics.RetrieveUpdateDestroyAPIView):
|
class SalesOrderDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for detail view of a SalesOrder object.
|
API endpoint for detail view of a SalesOrder object.
|
||||||
"""
|
"""
|
||||||
@ -646,9 +714,9 @@ class SODetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class SOLineItemFilter(rest_filters.FilterSet):
|
class SalesOrderLineItemFilter(rest_filters.FilterSet):
|
||||||
"""
|
"""
|
||||||
Custom filters for SOLineItemList endpoint
|
Custom filters for SalesOrderLineItemList endpoint
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -679,14 +747,14 @@ class SOLineItemFilter(rest_filters.FilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class SOLineItemList(generics.ListCreateAPIView):
|
class SalesOrderLineItemList(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for accessing a list of SalesOrderLineItem objects.
|
API endpoint for accessing a list of SalesOrderLineItem objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.SalesOrderLineItem.objects.all()
|
queryset = models.SalesOrderLineItem.objects.all()
|
||||||
serializer_class = serializers.SOLineItemSerializer
|
serializer_class = serializers.SalesOrderLineItemSerializer
|
||||||
filterset_class = SOLineItemFilter
|
filterset_class = SalesOrderLineItemFilter
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -743,11 +811,27 @@ class SOLineItemList(generics.ListCreateAPIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SOLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
class SalesOrderExtraLineList(GeneralExtraLineList, generics.ListCreateAPIView):
|
||||||
|
"""
|
||||||
|
API endpoint for accessing a list of SalesOrderExtraLine objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = models.SalesOrderExtraLine.objects.all()
|
||||||
|
serializer_class = serializers.SalesOrderExtraLineSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SalesOrderExtraLineDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
""" API endpoint for detail view of a SalesOrderExtraLine object """
|
||||||
|
|
||||||
|
queryset = models.SalesOrderExtraLine.objects.all()
|
||||||
|
serializer_class = serializers.SalesOrderExtraLineSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class SalesOrderLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
""" API endpoint for detail view of a SalesOrderLineItem object """
|
""" API endpoint for detail view of a SalesOrderLineItem object """
|
||||||
|
|
||||||
queryset = models.SalesOrderLineItem.objects.all()
|
queryset = models.SalesOrderLineItem.objects.all()
|
||||||
serializer_class = serializers.SOLineItemSerializer
|
serializer_class = serializers.SalesOrderLineItemSerializer
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderComplete(generics.CreateAPIView):
|
class SalesOrderComplete(generics.CreateAPIView):
|
||||||
@ -779,7 +863,7 @@ class SalesOrderAllocateSerials(generics.CreateAPIView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.SalesOrder.objects.none()
|
queryset = models.SalesOrder.objects.none()
|
||||||
serializer_class = serializers.SOSerialAllocationSerializer
|
serializer_class = serializers.SalesOrderSerialAllocationSerializer
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
|
|
||||||
@ -801,11 +885,11 @@ class SalesOrderAllocate(generics.CreateAPIView):
|
|||||||
API endpoint to allocate stock items against a SalesOrder
|
API endpoint to allocate stock items against a SalesOrder
|
||||||
|
|
||||||
- The SalesOrder is specified in the URL
|
- The SalesOrder is specified in the URL
|
||||||
- See the SOShipmentAllocationSerializer class
|
- See the SalesOrderShipmentAllocationSerializer class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.SalesOrder.objects.none()
|
queryset = models.SalesOrder.objects.none()
|
||||||
serializer_class = serializers.SOShipmentAllocationSerializer
|
serializer_class = serializers.SalesOrderShipmentAllocationSerializer
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
|
|
||||||
@ -822,7 +906,7 @@ class SalesOrderAllocate(generics.CreateAPIView):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class SOAllocationDetail(generics.RetrieveUpdateDestroyAPIView):
|
class SalesOrderAllocationDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for detali view of a SalesOrderAllocation object
|
API endpoint for detali view of a SalesOrderAllocation object
|
||||||
"""
|
"""
|
||||||
@ -831,7 +915,7 @@ class SOAllocationDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
serializer_class = serializers.SalesOrderAllocationSerializer
|
serializer_class = serializers.SalesOrderAllocationSerializer
|
||||||
|
|
||||||
|
|
||||||
class SOAllocationList(generics.ListAPIView):
|
class SalesOrderAllocationList(generics.ListAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for listing SalesOrderAllocation objects
|
API endpoint for listing SalesOrderAllocation objects
|
||||||
"""
|
"""
|
||||||
@ -909,9 +993,9 @@ class SOAllocationList(generics.ListAPIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SOShipmentFilter(rest_filters.FilterSet):
|
class SalesOrderShipmentFilter(rest_filters.FilterSet):
|
||||||
"""
|
"""
|
||||||
Custom filterset for the SOShipmentList endpoint
|
Custom filterset for the SalesOrderShipmentList endpoint
|
||||||
"""
|
"""
|
||||||
|
|
||||||
shipped = rest_filters.BooleanFilter(label='shipped', method='filter_shipped')
|
shipped = rest_filters.BooleanFilter(label='shipped', method='filter_shipped')
|
||||||
@ -934,21 +1018,21 @@ class SOShipmentFilter(rest_filters.FilterSet):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SOShipmentList(generics.ListCreateAPIView):
|
class SalesOrderShipmentList(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
API list endpoint for SalesOrderShipment model
|
API list endpoint for SalesOrderShipment model
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.SalesOrderShipment.objects.all()
|
queryset = models.SalesOrderShipment.objects.all()
|
||||||
serializer_class = serializers.SalesOrderShipmentSerializer
|
serializer_class = serializers.SalesOrderShipmentSerializer
|
||||||
filterset_class = SOShipmentFilter
|
filterset_class = SalesOrderShipmentFilter
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
rest_filters.DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SOShipmentDetail(generics.RetrieveUpdateDestroyAPIView):
|
class SalesOrderShipmentDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
API detail endpooint for SalesOrderShipment model
|
API detail endpooint for SalesOrderShipment model
|
||||||
"""
|
"""
|
||||||
@ -957,7 +1041,7 @@ class SOShipmentDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
serializer_class = serializers.SalesOrderShipmentSerializer
|
serializer_class = serializers.SalesOrderShipmentSerializer
|
||||||
|
|
||||||
|
|
||||||
class SOShipmentComplete(generics.CreateAPIView):
|
class SalesOrderShipmentComplete(generics.CreateAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for completing (shipping) a SalesOrderShipment
|
API endpoint for completing (shipping) a SalesOrderShipment
|
||||||
"""
|
"""
|
||||||
@ -983,13 +1067,13 @@ class SOShipmentComplete(generics.CreateAPIView):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
class PurchaseOrderAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
||||||
"""
|
"""
|
||||||
API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload)
|
API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.PurchaseOrderAttachment.objects.all()
|
queryset = models.PurchaseOrderAttachment.objects.all()
|
||||||
serializer_class = serializers.POAttachmentSerializer
|
serializer_class = serializers.PurchaseOrderAttachmentSerializer
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
rest_filters.DjangoFilterBackend,
|
rest_filters.DjangoFilterBackend,
|
||||||
@ -1000,13 +1084,13 @@ class POAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class POAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
|
class PurchaseOrderAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
|
||||||
"""
|
"""
|
||||||
Detail endpoint for a PurchaseOrderAttachment
|
Detail endpoint for a PurchaseOrderAttachment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = models.PurchaseOrderAttachment.objects.all()
|
queryset = models.PurchaseOrderAttachment.objects.all()
|
||||||
serializer_class = serializers.POAttachmentSerializer
|
serializer_class = serializers.PurchaseOrderAttachmentSerializer
|
||||||
|
|
||||||
|
|
||||||
order_api_urls = [
|
order_api_urls = [
|
||||||
@ -1016,39 +1100,45 @@ order_api_urls = [
|
|||||||
|
|
||||||
# Purchase order attachments
|
# Purchase order attachments
|
||||||
url(r'attachment/', include([
|
url(r'attachment/', include([
|
||||||
url(r'^(?P<pk>\d+)/$', POAttachmentDetail.as_view(), name='api-po-attachment-detail'),
|
url(r'^(?P<pk>\d+)/$', PurchaseOrderAttachmentDetail.as_view(), name='api-po-attachment-detail'),
|
||||||
url(r'^.*$', POAttachmentList.as_view(), name='api-po-attachment-list'),
|
url(r'^.*$', PurchaseOrderAttachmentList.as_view(), name='api-po-attachment-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# Individual purchase order detail URLs
|
# Individual purchase order detail URLs
|
||||||
url(r'^(?P<pk>\d+)/', include([
|
url(r'^(?P<pk>\d+)/', include([
|
||||||
url(r'^receive/', POReceive.as_view(), name='api-po-receive'),
|
url(r'^receive/', PurchaseOrderReceive.as_view(), name='api-po-receive'),
|
||||||
url(r'.*$', PODetail.as_view(), name='api-po-detail'),
|
url(r'.*$', PurchaseOrderDetail.as_view(), name='api-po-detail'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# Purchase order list
|
# Purchase order list
|
||||||
url(r'^.*$', POList.as_view(), name='api-po-list'),
|
url(r'^.*$', PurchaseOrderList.as_view(), name='api-po-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# API endpoints for purchase order line items
|
# API endpoints for purchase order line items
|
||||||
url(r'^po-line/', include([
|
url(r'^po-line/', include([
|
||||||
url(r'^(?P<pk>\d+)/$', POLineItemDetail.as_view(), name='api-po-line-detail'),
|
url(r'^(?P<pk>\d+)/$', PurchaseOrderLineItemDetail.as_view(), name='api-po-line-detail'),
|
||||||
url(r'^.*$', POLineItemList.as_view(), name='api-po-line-list'),
|
url(r'^.*$', PurchaseOrderLineItemList.as_view(), name='api-po-line-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# API endpoints for sales orders
|
# API endpoints for purchase order extra line
|
||||||
|
url(r'^po-extra-line/', include([
|
||||||
|
url(r'^(?P<pk>\d+)/$', PurchaseOrderExtraLineDetail.as_view(), name='api-po-extra-line-detail'),
|
||||||
|
url(r'^$', PurchaseOrderExtraLineList.as_view(), name='api-po-extra-line-list'),
|
||||||
|
])),
|
||||||
|
|
||||||
|
# API endpoints for sales ordesr
|
||||||
url(r'^so/', include([
|
url(r'^so/', include([
|
||||||
url(r'attachment/', include([
|
url(r'attachment/', include([
|
||||||
url(r'^(?P<pk>\d+)/$', SOAttachmentDetail.as_view(), name='api-so-attachment-detail'),
|
url(r'^(?P<pk>\d+)/$', SalesOrderAttachmentDetail.as_view(), name='api-so-attachment-detail'),
|
||||||
url(r'^.*$', SOAttachmentList.as_view(), name='api-so-attachment-list'),
|
url(r'^.*$', SalesOrderAttachmentList.as_view(), name='api-so-attachment-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
url(r'^shipment/', include([
|
url(r'^shipment/', include([
|
||||||
url(r'^(?P<pk>\d+)/', include([
|
url(r'^(?P<pk>\d+)/', include([
|
||||||
url(r'^ship/$', SOShipmentComplete.as_view(), name='api-so-shipment-ship'),
|
url(r'^ship/$', SalesOrderShipmentComplete.as_view(), name='api-so-shipment-ship'),
|
||||||
url(r'^.*$', SOShipmentDetail.as_view(), name='api-so-shipment-detail'),
|
url(r'^.*$', SalesOrderShipmentDetail.as_view(), name='api-so-shipment-detail'),
|
||||||
])),
|
])),
|
||||||
url(r'^.*$', SOShipmentList.as_view(), name='api-so-shipment-list'),
|
url(r'^.*$', SalesOrderShipmentList.as_view(), name='api-so-shipment-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# Sales order detail view
|
# Sales order detail view
|
||||||
@ -1056,22 +1146,28 @@ order_api_urls = [
|
|||||||
url(r'^complete/', SalesOrderComplete.as_view(), name='api-so-complete'),
|
url(r'^complete/', SalesOrderComplete.as_view(), name='api-so-complete'),
|
||||||
url(r'^allocate/', SalesOrderAllocate.as_view(), name='api-so-allocate'),
|
url(r'^allocate/', SalesOrderAllocate.as_view(), name='api-so-allocate'),
|
||||||
url(r'^allocate-serials/', SalesOrderAllocateSerials.as_view(), name='api-so-allocate-serials'),
|
url(r'^allocate-serials/', SalesOrderAllocateSerials.as_view(), name='api-so-allocate-serials'),
|
||||||
url(r'^.*$', SODetail.as_view(), name='api-so-detail'),
|
url(r'^.*$', SalesOrderDetail.as_view(), name='api-so-detail'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# Sales order list view
|
# Sales order list view
|
||||||
url(r'^.*$', SOList.as_view(), name='api-so-list'),
|
url(r'^.*$', SalesOrderList.as_view(), name='api-so-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# API endpoints for sales order line items
|
# API endpoints for sales order line items
|
||||||
url(r'^so-line/', include([
|
url(r'^so-line/', include([
|
||||||
url(r'^(?P<pk>\d+)/$', SOLineItemDetail.as_view(), name='api-so-line-detail'),
|
url(r'^(?P<pk>\d+)/$', SalesOrderLineItemDetail.as_view(), name='api-so-line-detail'),
|
||||||
url(r'^$', SOLineItemList.as_view(), name='api-so-line-list'),
|
url(r'^$', SalesOrderLineItemList.as_view(), name='api-so-line-list'),
|
||||||
|
])),
|
||||||
|
|
||||||
|
# API endpoints for sales order extra line
|
||||||
|
url(r'^so-extra-line/', include([
|
||||||
|
url(r'^(?P<pk>\d+)/$', SalesOrderExtraLineDetail.as_view(), name='api-so-extra-line-detail'),
|
||||||
|
url(r'^$', SalesOrderExtraLineList.as_view(), name='api-so-extra-line-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# API endpoints for sales order allocations
|
# API endpoints for sales order allocations
|
||||||
url(r'^so-allocation/', include([
|
url(r'^so-allocation/', include([
|
||||||
url(r'^(?P<pk>\d+)/$', SOAllocationDetail.as_view(), name='api-so-allocation-detail'),
|
url(r'^(?P<pk>\d+)/$', SalesOrderAllocationDetail.as_view(), name='api-so-allocation-detail'),
|
||||||
url(r'^.*$', SOAllocationList.as_view(), name='api-so-allocation-list'),
|
url(r'^.*$', SalesOrderAllocationList.as_view(), name='api-so-allocation-list'),
|
||||||
])),
|
])),
|
||||||
]
|
]
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-03-27 01:11
|
||||||
|
|
||||||
|
import InvenTree.fields
|
||||||
|
import django.core.validators
|
||||||
|
from django.core import serializers
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import djmoney.models.fields
|
||||||
|
import djmoney.models.validators
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_model(apps, line_item_ref, extra_line_ref, price_ref):
|
||||||
|
"""Convert the OrderLineItem instances if applicable to new ExtraLine instances"""
|
||||||
|
OrderLineItem = apps.get_model('order', line_item_ref)
|
||||||
|
OrderExtraLine = apps.get_model('order', extra_line_ref)
|
||||||
|
|
||||||
|
items_to_change = OrderLineItem.objects.filter(part=None)
|
||||||
|
if items_to_change.count() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f'\nFound {items_to_change.count()} old {line_item_ref} instance(s)')
|
||||||
|
print(f'Starting to convert - currently at {OrderExtraLine.objects.all().count()} {extra_line_ref} / {OrderLineItem.objects.all().count()} {line_item_ref} instance(s)')
|
||||||
|
for lineItem in items_to_change:
|
||||||
|
newitem = OrderExtraLine(
|
||||||
|
order=lineItem.order,
|
||||||
|
notes=lineItem.notes,
|
||||||
|
price=getattr(lineItem, price_ref),
|
||||||
|
quantity=lineItem.quantity,
|
||||||
|
reference=lineItem.reference,
|
||||||
|
)
|
||||||
|
newitem.context = {'migration': serializers.serialize('json', [lineItem, ])}
|
||||||
|
newitem.save()
|
||||||
|
|
||||||
|
lineItem.delete()
|
||||||
|
print(f'Done converting line items - now at {OrderExtraLine.objects.all().count()} {extra_line_ref} / {OrderLineItem.objects.all().count()} {line_item_ref} instance(s)')
|
||||||
|
|
||||||
|
|
||||||
|
def _reconvert_model(apps, line_item_ref, extra_line_ref):
|
||||||
|
"""Convert ExtraLine instances back to OrderLineItem instances"""
|
||||||
|
OrderLineItem = apps.get_model('order', line_item_ref)
|
||||||
|
OrderExtraLine = apps.get_model('order', extra_line_ref)
|
||||||
|
|
||||||
|
print(f'\nStarting to convert - currently at {OrderExtraLine.objects.all().count()} {extra_line_ref} / {OrderLineItem.objects.all().count()} {line_item_ref} instance(s)')
|
||||||
|
for extra_line in OrderExtraLine.objects.all():
|
||||||
|
# regenreate item
|
||||||
|
if extra_line.context:
|
||||||
|
context_string = getattr(extra_line.context, 'migration')
|
||||||
|
if not context_string:
|
||||||
|
continue
|
||||||
|
[item.save() for item in serializers.deserialize('json', context_string)]
|
||||||
|
extra_line.delete()
|
||||||
|
print(f'Done converting line items - now at {OrderExtraLine.objects.all().count()} {extra_line_ref} / {OrderLineItem.objects.all().count()} {line_item_ref} instance(s)')
|
||||||
|
|
||||||
|
|
||||||
|
def convert_line_items(apps, schema_editor):
|
||||||
|
"""convert line items"""
|
||||||
|
_convert_model(apps, 'PurchaseOrderLineItem', 'PurchaseOrderExtraLine', 'purchase_price')
|
||||||
|
_convert_model(apps, 'SalesOrderLineItem', 'SalesOrderExtraLine', 'sale_price')
|
||||||
|
|
||||||
|
|
||||||
|
def nunconvert_line_items(apps, schema_editor): # pragma: no cover
|
||||||
|
"""reconvert line items"""
|
||||||
|
_reconvert_model(apps, 'PurchaseOrderLineItem', 'PurchaseOrderExtraLine')
|
||||||
|
_reconvert_model(apps, 'SalesOrderLineItem', 'SalesOrderExtraLine')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('order', '0063_alter_purchaseorderlineitem_unique_together'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SalesOrderExtraLine',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('quantity', InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Item quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity')),
|
||||||
|
('reference', models.CharField(blank=True, help_text='Line item reference', max_length=100, verbose_name='Reference')),
|
||||||
|
('notes', models.CharField(blank=True, help_text='Line item notes', max_length=500, verbose_name='Notes')),
|
||||||
|
('target_date', models.DateField(blank=True, help_text='Target shipping date for this line item', null=True, verbose_name='Target Date')),
|
||||||
|
('context', models.JSONField(blank=True, help_text='Additional context for this line', null=True, verbose_name='Context')),
|
||||||
|
('price_currency', djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3)),
|
||||||
|
('price', InvenTree.fields.InvenTreeModelMoneyField(blank=True, currency_choices=[], decimal_places=4, default_currency='', help_text='Unit price', max_digits=19, null=True, validators=[djmoney.models.validators.MinMoneyValidator(0)], verbose_name='Price')),
|
||||||
|
('order', models.ForeignKey(help_text='Sales Order', on_delete=django.db.models.deletion.CASCADE, related_name='extra_lines', to='order.salesorder', verbose_name='Order')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PurchaseOrderExtraLine',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('quantity', InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Item quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity')),
|
||||||
|
('reference', models.CharField(blank=True, help_text='Line item reference', max_length=100, verbose_name='Reference')),
|
||||||
|
('notes', models.CharField(blank=True, help_text='Line item notes', max_length=500, verbose_name='Notes')),
|
||||||
|
('target_date', models.DateField(blank=True, help_text='Target shipping date for this line item', null=True, verbose_name='Target Date')),
|
||||||
|
('context', models.JSONField(blank=True, help_text='Additional context for this line', null=True, verbose_name='Context')),
|
||||||
|
('price_currency', djmoney.models.fields.CurrencyField(choices=[], default='', editable=False, max_length=3)),
|
||||||
|
('price', InvenTree.fields.InvenTreeModelMoneyField(blank=True, currency_choices=[], decimal_places=4, default_currency='', help_text='Unit price', max_digits=19, null=True, validators=[djmoney.models.validators.MinMoneyValidator(0)], verbose_name='Price')),
|
||||||
|
('order', models.ForeignKey(help_text='Purchase Order', on_delete=django.db.models.deletion.CASCADE, related_name='extra_lines', to='order.purchaseorder', verbose_name='Order')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RunPython(convert_line_items, reverse_code=nunconvert_line_items),
|
||||||
|
]
|
@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-03-28 22:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('company', '0042_supplierpricebreak_updated'),
|
||||||
|
('order', '0064_purchaseorderextraline_salesorderextraline'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='purchaseorderlineitem',
|
||||||
|
name='part',
|
||||||
|
field=models.ForeignKey(help_text='Supplier part', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchase_order_line_items', to='company.supplierpart', verbose_name='Part'),
|
||||||
|
),
|
||||||
|
]
|
@ -5,6 +5,7 @@ Order model definitions
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
@ -21,6 +22,10 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from markdownx.models import MarkdownxField
|
from markdownx.models import MarkdownxField
|
||||||
from mptt.models import TreeForeignKey
|
from mptt.models import TreeForeignKey
|
||||||
|
|
||||||
|
from djmoney.contrib.exchange.models import convert_money
|
||||||
|
from djmoney.money import Money
|
||||||
|
from common.settings import currency_code_default
|
||||||
|
|
||||||
from users import models as UserModels
|
from users import models as UserModels
|
||||||
from part import models as PartModels
|
from part import models as PartModels
|
||||||
from stock import models as stock_models
|
from stock import models as stock_models
|
||||||
@ -146,6 +151,25 @@ class Order(ReferenceIndexingMixin):
|
|||||||
|
|
||||||
notes = MarkdownxField(blank=True, verbose_name=_('Notes'), help_text=_('Order notes'))
|
notes = MarkdownxField(blank=True, verbose_name=_('Notes'), help_text=_('Order notes'))
|
||||||
|
|
||||||
|
def get_total_price(self):
|
||||||
|
"""
|
||||||
|
Calculates the total price of all order lines
|
||||||
|
"""
|
||||||
|
target_currency = currency_code_default()
|
||||||
|
total = Money(0, target_currency)
|
||||||
|
|
||||||
|
# gather name reference
|
||||||
|
price_ref = 'sale_price' if isinstance(self, SalesOrder) else 'purchase_price'
|
||||||
|
# order items
|
||||||
|
total += sum([a.quantity * convert_money(getattr(a, price_ref), target_currency) for a in self.lines.all() if getattr(a, price_ref)])
|
||||||
|
|
||||||
|
# extra lines
|
||||||
|
total += sum([a.quantity * convert_money(a.price, target_currency) for a in self.extra_lines.all() if a.price])
|
||||||
|
|
||||||
|
# set decimal-places
|
||||||
|
total.decimal_places = 4
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrder(Order):
|
class PurchaseOrder(Order):
|
||||||
""" A PurchaseOrder represents goods shipped inwards from an external supplier.
|
""" A PurchaseOrder represents goods shipped inwards from an external supplier.
|
||||||
@ -285,7 +309,7 @@ class PurchaseOrder(Order):
|
|||||||
raise ValidationError({'supplier': _("Part supplier must match PO supplier")})
|
raise ValidationError({'supplier': _("Part supplier must match PO supplier")})
|
||||||
|
|
||||||
if group:
|
if group:
|
||||||
# Check if there is already a matching line item (for this PO)
|
# Check if there is already a matching line item (for this PurchaseOrder)
|
||||||
matches = self.lines.filter(part=supplier_part)
|
matches = self.lines.filter(part=supplier_part)
|
||||||
|
|
||||||
if matches.count() > 0:
|
if matches.count() > 0:
|
||||||
@ -400,7 +424,7 @@ class PurchaseOrder(Order):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def receive_line_item(self, line, location, quantity, user, status=StockStatus.OK, **kwargs):
|
def receive_line_item(self, line, location, quantity, user, status=StockStatus.OK, **kwargs):
|
||||||
"""
|
"""
|
||||||
Receive a line item (or partial line item) against this PO
|
Receive a line item (or partial line item) against this PurchaseOrder
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Extract optional batch code for the new stock item
|
# Extract optional batch code for the new stock item
|
||||||
@ -851,12 +875,44 @@ class OrderLineItem(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderExtraLine(OrderLineItem):
|
||||||
|
"""
|
||||||
|
Abstract Model for a single ExtraLine in a Order
|
||||||
|
Attributes:
|
||||||
|
price: The unit sale price for this OrderLineItem
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
unique_together = [
|
||||||
|
]
|
||||||
|
|
||||||
|
context = models.JSONField(
|
||||||
|
blank=True, null=True,
|
||||||
|
verbose_name=_('Context'),
|
||||||
|
help_text=_('Additional context for this line'),
|
||||||
|
)
|
||||||
|
|
||||||
|
price = InvenTreeModelMoneyField(
|
||||||
|
max_digits=19,
|
||||||
|
decimal_places=4,
|
||||||
|
null=True, blank=True,
|
||||||
|
verbose_name=_('Price'),
|
||||||
|
help_text=_('Unit price'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def price_converted(self):
|
||||||
|
return convert_money(self.price, currency_code_default())
|
||||||
|
|
||||||
|
def price_converted_currency(self):
|
||||||
|
return currency_code_default()
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderLineItem(OrderLineItem):
|
class PurchaseOrderLineItem(OrderLineItem):
|
||||||
""" Model for a purchase order line item.
|
""" Model for a purchase order line item.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
order: Reference to a PurchaseOrder object
|
order: Reference to a PurchaseOrder object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -903,11 +959,9 @@ class PurchaseOrderLineItem(OrderLineItem):
|
|||||||
else:
|
else:
|
||||||
return self.part.part
|
return self.part.part
|
||||||
|
|
||||||
# TODO - Function callback for when the SupplierPart is deleted?
|
|
||||||
|
|
||||||
part = models.ForeignKey(
|
part = models.ForeignKey(
|
||||||
SupplierPart, on_delete=models.SET_NULL,
|
SupplierPart, on_delete=models.SET_NULL,
|
||||||
blank=True, null=True,
|
blank=False, null=True,
|
||||||
related_name='purchase_order_line_items',
|
related_name='purchase_order_line_items',
|
||||||
verbose_name=_('Part'),
|
verbose_name=_('Part'),
|
||||||
help_text=_("Supplier part"),
|
help_text=_("Supplier part"),
|
||||||
@ -960,6 +1014,21 @@ class PurchaseOrderLineItem(OrderLineItem):
|
|||||||
return max(r, 0)
|
return max(r, 0)
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderExtraLine(OrderExtraLine):
|
||||||
|
"""
|
||||||
|
Model for a single ExtraLine in a PurchaseOrder
|
||||||
|
Attributes:
|
||||||
|
order: Link to the PurchaseOrder that this line belongs to
|
||||||
|
title: title of line
|
||||||
|
price: The unit price for this OrderLine
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def get_api_url():
|
||||||
|
return reverse('api-po-extra-line-list')
|
||||||
|
|
||||||
|
order = models.ForeignKey(PurchaseOrder, on_delete=models.CASCADE, related_name='extra_lines', verbose_name=_('Order'), help_text=_('Purchase Order'))
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderLineItem(OrderLineItem):
|
class SalesOrderLineItem(OrderLineItem):
|
||||||
"""
|
"""
|
||||||
Model for a single LineItem in a SalesOrder
|
Model for a single LineItem in a SalesOrder
|
||||||
@ -1163,6 +1232,21 @@ class SalesOrderShipment(models.Model):
|
|||||||
trigger_event('salesordershipment.completed', id=self.pk)
|
trigger_event('salesordershipment.completed', id=self.pk)
|
||||||
|
|
||||||
|
|
||||||
|
class SalesOrderExtraLine(OrderExtraLine):
|
||||||
|
"""
|
||||||
|
Model for a single ExtraLine in a SalesOrder
|
||||||
|
Attributes:
|
||||||
|
order: Link to the SalesOrder that this line belongs to
|
||||||
|
title: title of line
|
||||||
|
price: The unit price for this OrderLine
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def get_api_url():
|
||||||
|
return reverse('api-so-extra-line-list')
|
||||||
|
|
||||||
|
order = models.ForeignKey(SalesOrder, on_delete=models.CASCADE, related_name='extra_lines', verbose_name=_('Order'), help_text=_('Sales Order'))
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderAllocation(models.Model):
|
class SalesOrderAllocation(models.Model):
|
||||||
"""
|
"""
|
||||||
This model is used to 'allocate' stock items to a SalesOrder.
|
This model is used to 'allocate' stock items to a SalesOrder.
|
||||||
|
@ -40,7 +40,64 @@ import stock.serializers
|
|||||||
from users.serializers import OwnerSerializer
|
from users.serializers import OwnerSerializer
|
||||||
|
|
||||||
|
|
||||||
class POSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
|
class AbstractOrderSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Abstract field definitions for OrderSerializers
|
||||||
|
"""
|
||||||
|
total_price = InvenTreeMoneySerializer(
|
||||||
|
source='get_total_price',
|
||||||
|
allow_null=True,
|
||||||
|
read_only=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
total_price_string = serializers.CharField(source='get_total_price', read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractExtraLineSerializer(serializers.Serializer):
|
||||||
|
""" Abstract Serializer for a ExtraLine object """
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
order_detail = kwargs.pop('order_detail', False)
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if order_detail is not True:
|
||||||
|
self.fields.pop('order_detail')
|
||||||
|
|
||||||
|
quantity = serializers.FloatField()
|
||||||
|
|
||||||
|
price = InvenTreeMoneySerializer(
|
||||||
|
allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
price_string = serializers.CharField(source='price', read_only=True)
|
||||||
|
|
||||||
|
price_currency = serializers.ChoiceField(
|
||||||
|
choices=currency_code_mappings(),
|
||||||
|
help_text=_('Price currency'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractExtraLineMeta:
|
||||||
|
"""
|
||||||
|
Abstract Meta for ExtraLine
|
||||||
|
"""
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
'pk',
|
||||||
|
'quantity',
|
||||||
|
'reference',
|
||||||
|
'notes',
|
||||||
|
'context',
|
||||||
|
'order',
|
||||||
|
'order_detail',
|
||||||
|
'price',
|
||||||
|
'price_currency',
|
||||||
|
'price_string',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderSerializer(AbstractOrderSerializer, ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
|
||||||
""" Serializer for a PurchaseOrder object """
|
""" Serializer for a PurchaseOrder object """
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -110,6 +167,8 @@ class POSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
|
|||||||
'status_text',
|
'status_text',
|
||||||
'target_date',
|
'target_date',
|
||||||
'notes',
|
'notes',
|
||||||
|
'total_price',
|
||||||
|
'total_price_string',
|
||||||
]
|
]
|
||||||
|
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
@ -120,7 +179,7 @@ class POSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class POLineItemSerializer(InvenTreeModelSerializer):
|
class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def annotate_queryset(queryset):
|
def annotate_queryset(queryset):
|
||||||
@ -187,7 +246,7 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
help_text=_('Purchase price currency'),
|
help_text=_('Purchase price currency'),
|
||||||
)
|
)
|
||||||
|
|
||||||
order_detail = POSerializer(source='order', read_only=True, many=False)
|
order_detail = PurchaseOrderSerializer(source='order', read_only=True, many=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = order.models.PurchaseOrderLineItem
|
model = order.models.PurchaseOrderLineItem
|
||||||
@ -214,7 +273,16 @@ class POLineItemSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class POLineItemReceiveSerializer(serializers.Serializer):
|
class PurchaseOrderExtraLineSerializer(AbstractExtraLineSerializer, InvenTreeModelSerializer):
|
||||||
|
""" Serializer for a PurchaseOrderExtraLine object """
|
||||||
|
|
||||||
|
order_detail = PurchaseOrderSerializer(source='order', many=False, read_only=True)
|
||||||
|
|
||||||
|
class Meta(AbstractExtraLineMeta):
|
||||||
|
model = order.models.PurchaseOrderExtraLine
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderLineItemReceiveSerializer(serializers.Serializer):
|
||||||
"""
|
"""
|
||||||
A serializer for receiving a single purchase order line item against a purchase order
|
A serializer for receiving a single purchase order line item against a purchase order
|
||||||
"""
|
"""
|
||||||
@ -344,12 +412,12 @@ class POLineItemReceiveSerializer(serializers.Serializer):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class POReceiveSerializer(serializers.Serializer):
|
class PurchaseOrderReceiveSerializer(serializers.Serializer):
|
||||||
"""
|
"""
|
||||||
Serializer for receiving items against a purchase order
|
Serializer for receiving items against a purchase order
|
||||||
"""
|
"""
|
||||||
|
|
||||||
items = POLineItemReceiveSerializer(many=True)
|
items = PurchaseOrderLineItemReceiveSerializer(many=True)
|
||||||
|
|
||||||
location = serializers.PrimaryKeyRelatedField(
|
location = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=stock.models.StockLocation.objects.all(),
|
queryset=stock.models.StockLocation.objects.all(),
|
||||||
@ -444,7 +512,7 @@ class POReceiveSerializer(serializers.Serializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class POAttachmentSerializer(InvenTreeAttachmentSerializer):
|
class PurchaseOrderAttachmentSerializer(InvenTreeAttachmentSerializer):
|
||||||
"""
|
"""
|
||||||
Serializers for the PurchaseOrderAttachment model
|
Serializers for the PurchaseOrderAttachment model
|
||||||
"""
|
"""
|
||||||
@ -467,7 +535,7 @@ class POAttachmentSerializer(InvenTreeAttachmentSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
|
class SalesOrderSerializer(AbstractOrderSerializer, ReferenceIndexingSerializerMixin, InvenTreeModelSerializer):
|
||||||
"""
|
"""
|
||||||
Serializers for the SalesOrder object
|
Serializers for the SalesOrder object
|
||||||
"""
|
"""
|
||||||
@ -535,6 +603,8 @@ class SalesOrderSerializer(ReferenceIndexingSerializerMixin, InvenTreeModelSeria
|
|||||||
'status_text',
|
'status_text',
|
||||||
'shipment_date',
|
'shipment_date',
|
||||||
'target_date',
|
'target_date',
|
||||||
|
'total_price',
|
||||||
|
'total_price_string',
|
||||||
]
|
]
|
||||||
|
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
@ -612,7 +682,7 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SOLineItemSerializer(InvenTreeModelSerializer):
|
class SalesOrderLineItemSerializer(InvenTreeModelSerializer):
|
||||||
""" Serializer for a SalesOrderLineItem object """
|
""" Serializer for a SalesOrderLineItem object """
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -862,7 +932,7 @@ class SalesOrderCompleteSerializer(serializers.Serializer):
|
|||||||
order.complete_order(user)
|
order.complete_order(user)
|
||||||
|
|
||||||
|
|
||||||
class SOSerialAllocationSerializer(serializers.Serializer):
|
class SalesOrderSerialAllocationSerializer(serializers.Serializer):
|
||||||
"""
|
"""
|
||||||
DRF serializer for allocation of serial numbers against a sales order / shipment
|
DRF serializer for allocation of serial numbers against a sales order / shipment
|
||||||
"""
|
"""
|
||||||
@ -1025,7 +1095,7 @@ class SOSerialAllocationSerializer(serializers.Serializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SOShipmentAllocationSerializer(serializers.Serializer):
|
class SalesOrderShipmentAllocationSerializer(serializers.Serializer):
|
||||||
"""
|
"""
|
||||||
DRF serializer for allocation of stock items against a sales order / shipment
|
DRF serializer for allocation of stock items against a sales order / shipment
|
||||||
"""
|
"""
|
||||||
@ -1099,7 +1169,16 @@ class SOShipmentAllocationSerializer(serializers.Serializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SOAttachmentSerializer(InvenTreeAttachmentSerializer):
|
class SalesOrderExtraLineSerializer(AbstractExtraLineSerializer, InvenTreeModelSerializer):
|
||||||
|
""" Serializer for a SalesOrderExtraLine object """
|
||||||
|
|
||||||
|
order_detail = SalesOrderSerializer(source='order', many=False, read_only=True)
|
||||||
|
|
||||||
|
class Meta(AbstractExtraLineMeta):
|
||||||
|
model = order.models.SalesOrderExtraLine
|
||||||
|
|
||||||
|
|
||||||
|
class SalesOrderAttachmentSerializer(InvenTreeAttachmentSerializer):
|
||||||
"""
|
"""
|
||||||
Serializers for the SalesOrderAttachment model
|
Serializers for the SalesOrderAttachment model
|
||||||
"""
|
"""
|
||||||
|
@ -171,6 +171,12 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<td>{{ order.responsible }}</td>
|
<td>{{ order.responsible }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-dollar-sign'></span></td>
|
||||||
|
<td>{% trans "Total cost" %}</td>
|
||||||
|
<td id="poTotalPrice">{{ order.get_total_price }}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -42,6 +42,29 @@
|
|||||||
<table class='table table-striped table-condensed' id='po-line-table' data-toolbar='#order-toolbar-buttons'>
|
<table class='table table-striped table-condensed' id='po-line-table' data-toolbar='#order-toolbar-buttons'>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class='panel-heading'>
|
||||||
|
<div class='d-flex flex-wrap'>
|
||||||
|
<h4>{% trans "Extra Lines" %}</h4>
|
||||||
|
{% include "spacer.html" %}
|
||||||
|
<div class='btn-group' role='group'>
|
||||||
|
{% if roles.purchase_order.change and order.status == PurchaseOrderStatus.PENDING %}
|
||||||
|
<button type='button' class='btn btn-success' id='new-po-extra-line'>
|
||||||
|
<span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='panel-content'>
|
||||||
|
<div id='order-extra-toolbar-buttons' class='btn-group' style='float: right;'>
|
||||||
|
<div class='btn-group'>
|
||||||
|
{% include "filter_list.html" with id="purchase-order-extra-lines" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class='table table-striped table-condensed' id='po-extra-lines-table' data-toolbar='#order-extra-toolbar-buttons'>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='panel panel-hidden' id='panel-received-items'>
|
<div class='panel panel-hidden' id='panel-received-items'>
|
||||||
@ -200,6 +223,37 @@ loadPurchaseOrderLineItemTable('#po-line-table', {
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#new-po-extra-line").click(function() {
|
||||||
|
|
||||||
|
var fields = extraLineFields({
|
||||||
|
order: {{ order.pk }},
|
||||||
|
});
|
||||||
|
|
||||||
|
constructForm('{% url "api-po-extra-line-list" %}', {
|
||||||
|
fields: fields,
|
||||||
|
method: 'POST',
|
||||||
|
title: '{% trans "Add Order Line" %}',
|
||||||
|
onSuccess: function() {
|
||||||
|
$("#po-extra-lines-table").bootstrapTable("refresh");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
loadPurchaseOrderExtraLineTable(
|
||||||
|
'#po-extra-lines-table',
|
||||||
|
{
|
||||||
|
order: {{ order.pk }},
|
||||||
|
status: {{ order.status }},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
loadOrderTotal(
|
||||||
|
'#poTotalPrice',
|
||||||
|
{
|
||||||
|
url: '{% url "api-po-detail" order.pk %}',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
enableSidebar('purchaseorder');
|
enableSidebar('purchaseorder');
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -183,6 +183,12 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<td>{{ order.responsible }}</td>
|
<td>{{ order.responsible }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><span class='fas fa-dollar-sign'></span></td>
|
||||||
|
<td>{% trans "Total cost" %}</td>
|
||||||
|
<td id="soTotalPrice">{{ order.get_total_price }}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -34,6 +34,29 @@
|
|||||||
<table class='table table-striped table-condensed' id='so-lines-table' data-toolbar='#order-toolbar-buttons'>
|
<table class='table table-striped table-condensed' id='so-lines-table' data-toolbar='#order-toolbar-buttons'>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class='panel-heading'>
|
||||||
|
<div class='d-flex flex-wrap'>
|
||||||
|
<h4>{% trans "Extra Lines" %}</h4>
|
||||||
|
{% include "spacer.html" %}
|
||||||
|
<div class='btn-group' role='group'>
|
||||||
|
{% if roles.sales_order.change and order.is_pending %}
|
||||||
|
<button type='button' class='btn btn-success' id='new-so-extra-line'>
|
||||||
|
<span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='panel-content'>
|
||||||
|
<div id='order-extra-toolbar-buttons' class='btn-group' style='float: right;'>
|
||||||
|
<div class='btn-group'>
|
||||||
|
{% include "filter_list.html" with id="sales-order-extra-lines" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class='table table-striped table-condensed' id='so-extra-lines-table' data-toolbar='#order-extra-toolbar-buttons'>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if order.is_pending %}
|
{% if order.is_pending %}
|
||||||
@ -238,6 +261,37 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$("#new-so-extra-line").click(function() {
|
||||||
|
|
||||||
|
var fields = extraLineFields({
|
||||||
|
order: {{ order.pk }},
|
||||||
|
});
|
||||||
|
|
||||||
|
constructForm('{% url "api-so-extra-line-list" %}', {
|
||||||
|
fields: fields,
|
||||||
|
method: 'POST',
|
||||||
|
title: '{% trans "Add Extra Line" %}',
|
||||||
|
onSuccess: function() {
|
||||||
|
$("#so-extra-lines-table").bootstrapTable("refresh");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
loadSalesOrderExtraLineTable(
|
||||||
|
'#so-extra-lines-table',
|
||||||
|
{
|
||||||
|
order: {{ order.pk }},
|
||||||
|
status: {{ order.status }},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
loadOrderTotal(
|
||||||
|
'#soTotalPrice',
|
||||||
|
{
|
||||||
|
url: '{% url "api-so-detail" order.pk %}',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
enableSidebar('salesorder');
|
enableSidebar('salesorder');
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -63,7 +63,7 @@ class PurchaseOrderTest(OrderTest):
|
|||||||
|
|
||||||
def test_po_list(self):
|
def test_po_list(self):
|
||||||
|
|
||||||
# List *ALL* PO items
|
# List *ALL* PurchaseOrder items
|
||||||
self.filter({}, 7)
|
self.filter({}, 7)
|
||||||
|
|
||||||
# Filter by supplier
|
# Filter by supplier
|
||||||
@ -175,7 +175,7 @@ class PurchaseOrderTest(OrderTest):
|
|||||||
|
|
||||||
pk = response.data['pk']
|
pk = response.data['pk']
|
||||||
|
|
||||||
# Try to create a PO with identical reference (should fail!)
|
# Try to create a PurchaseOrder with identical reference (should fail!)
|
||||||
response = self.post(
|
response = self.post(
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
@ -493,7 +493,7 @@ class PurchaseOrderReceiveTest(OrderTest):
|
|||||||
|
|
||||||
self.assertIn('can only be received against', str(response.data))
|
self.assertIn('can only be received against', str(response.data))
|
||||||
|
|
||||||
# Now, set the PO back to "PLACED" so the items can be received
|
# Now, set the PurchaseOrder back to "PLACED" so the items can be received
|
||||||
order.status = PurchaseOrderStatus.PLACED
|
order.status = PurchaseOrderStatus.PLACED
|
||||||
order.save()
|
order.save()
|
||||||
|
|
||||||
|
@ -122,3 +122,97 @@ class TestShipmentMigration(MigratorTestCase):
|
|||||||
# Check that the correct number of Shipments have been created
|
# Check that the correct number of Shipments have been created
|
||||||
self.assertEqual(SalesOrder.objects.count(), 5)
|
self.assertEqual(SalesOrder.objects.count(), 5)
|
||||||
self.assertEqual(Shipment.objects.count(), 5)
|
self.assertEqual(Shipment.objects.count(), 5)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdditionalLineMigration(MigratorTestCase):
|
||||||
|
"""
|
||||||
|
Test entire schema migration
|
||||||
|
"""
|
||||||
|
|
||||||
|
migrate_from = ('order', '0063_alter_purchaseorderlineitem_unique_together')
|
||||||
|
migrate_to = ('order', '0064_purchaseorderextraline_salesorderextraline')
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
"""
|
||||||
|
Create initial data set
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create a purchase order from a supplier
|
||||||
|
Company = self.old_state.apps.get_model('company', 'company')
|
||||||
|
PurchaseOrder = self.old_state.apps.get_model('order', 'purchaseorder')
|
||||||
|
Part = self.old_state.apps.get_model('part', 'part')
|
||||||
|
Supplierpart = self.old_state.apps.get_model('company', 'supplierpart')
|
||||||
|
# TODO @matmair fix this test!!!
|
||||||
|
# SalesOrder = self.old_state.apps.get_model('order', 'salesorder')
|
||||||
|
|
||||||
|
supplier = Company.objects.create(
|
||||||
|
name='Supplier A',
|
||||||
|
description='A great supplier!',
|
||||||
|
is_supplier=True,
|
||||||
|
is_customer=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
part = Part.objects.create(
|
||||||
|
name='Bob',
|
||||||
|
description='Can we build it?',
|
||||||
|
assembly=True,
|
||||||
|
salable=True,
|
||||||
|
purchaseable=False,
|
||||||
|
tree_id=0,
|
||||||
|
level=0,
|
||||||
|
lft=0,
|
||||||
|
rght=0,
|
||||||
|
)
|
||||||
|
supplierpart = Supplierpart.objects.create(
|
||||||
|
part=part,
|
||||||
|
supplier=supplier
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create some orders
|
||||||
|
for ii in range(10):
|
||||||
|
|
||||||
|
order = PurchaseOrder.objects.create(
|
||||||
|
supplier=supplier,
|
||||||
|
reference=f"{ii}-abcde",
|
||||||
|
description="Just a test order"
|
||||||
|
)
|
||||||
|
order.lines.create(
|
||||||
|
part=supplierpart,
|
||||||
|
quantity=12,
|
||||||
|
received=1
|
||||||
|
)
|
||||||
|
order.lines.create(
|
||||||
|
quantity=12,
|
||||||
|
received=1
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO @matmair fix this test!!!
|
||||||
|
# sales_order = SalesOrder.objects.create(
|
||||||
|
# customer=supplier,
|
||||||
|
# reference=f"{ii}-xyz",
|
||||||
|
# description="A test sales order",
|
||||||
|
# )
|
||||||
|
# sales_order.lines.create(
|
||||||
|
# part=part,
|
||||||
|
# quantity=12,
|
||||||
|
# received=1
|
||||||
|
# )
|
||||||
|
|
||||||
|
def test_po_migration(self):
|
||||||
|
"""
|
||||||
|
Test that the the PO lines where converted correctly
|
||||||
|
"""
|
||||||
|
|
||||||
|
PurchaseOrder = self.new_state.apps.get_model('order', 'purchaseorder')
|
||||||
|
for ii in range(10):
|
||||||
|
|
||||||
|
po = PurchaseOrder.objects.get(reference=f"{ii}-abcde")
|
||||||
|
self.assertEqual(po.extra_lines.count(), 1)
|
||||||
|
self.assertEqual(po.lines.count(), 1)
|
||||||
|
|
||||||
|
# TODO @matmair fix this test!!!
|
||||||
|
# SalesOrder = self.new_state.apps.get_model('order', 'salesorder')
|
||||||
|
# for ii in range(10):
|
||||||
|
# so = SalesOrder.objects.get(reference=f"{ii}-xyz")
|
||||||
|
# self.assertEqual(so.extra_lines, 1)
|
||||||
|
# self.assertEqual(so.lines.count(), 1)
|
||||||
|
@ -20,7 +20,7 @@ from decimal import Decimal, InvalidOperation
|
|||||||
|
|
||||||
from .models import PurchaseOrder, PurchaseOrderLineItem
|
from .models import PurchaseOrder, PurchaseOrderLineItem
|
||||||
from .models import SalesOrder, SalesOrderLineItem
|
from .models import SalesOrder, SalesOrderLineItem
|
||||||
from .admin import POLineItemResource, SOLineItemResource
|
from .admin import PurchaseOrderLineItemResource, SalesOrderLineItemResource
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
from company.models import Company, SupplierPart # ManufacturerPart
|
from company.models import Company, SupplierPart # ManufacturerPart
|
||||||
from stock.models import StockItem
|
from stock.models import StockItem
|
||||||
@ -410,7 +410,7 @@ class SalesOrderExport(AjaxView):
|
|||||||
|
|
||||||
filename = f"{str(order)} - {order.customer.name}.{export_format}"
|
filename = f"{str(order)} - {order.customer.name}.{export_format}"
|
||||||
|
|
||||||
dataset = SOLineItemResource().export(queryset=order.lines.all())
|
dataset = SalesOrderLineItemResource().export(queryset=order.lines.all())
|
||||||
|
|
||||||
filedata = dataset.export(format=export_format)
|
filedata = dataset.export(format=export_format)
|
||||||
|
|
||||||
@ -441,7 +441,7 @@ class PurchaseOrderExport(AjaxView):
|
|||||||
fmt=export_format
|
fmt=export_format
|
||||||
)
|
)
|
||||||
|
|
||||||
dataset = POLineItemResource().export(queryset=order.lines.all())
|
dataset = PurchaseOrderLineItemResource().export(queryset=order.lines.all())
|
||||||
|
|
||||||
filedata = dataset.export(format=export_format)
|
filedata = dataset.export(format=export_format)
|
||||||
|
|
||||||
@ -491,7 +491,7 @@ class OrderParts(AjaxView):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def get_suppliers(self):
|
def get_suppliers(self):
|
||||||
""" Calculates a list of suppliers which the user will need to create POs for.
|
""" Calculates a list of suppliers which the user will need to create PurchaseOrders for.
|
||||||
This is calculated AFTER the user finishes selecting the parts to order.
|
This is calculated AFTER the user finishes selecting the parts to order.
|
||||||
Crucially, get_parts() must be called before get_suppliers()
|
Crucially, get_parts() must be called before get_suppliers()
|
||||||
"""
|
"""
|
||||||
|
@ -31,8 +31,8 @@ from .models import SalesOrderReport
|
|||||||
from .serializers import TestReportSerializer
|
from .serializers import TestReportSerializer
|
||||||
from .serializers import BuildReportSerializer
|
from .serializers import BuildReportSerializer
|
||||||
from .serializers import BOMReportSerializer
|
from .serializers import BOMReportSerializer
|
||||||
from .serializers import POReportSerializer
|
from .serializers import PurchaseOrderReportSerializer
|
||||||
from .serializers import SOReportSerializer
|
from .serializers import SalesOrderReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class ReportListView(generics.ListAPIView):
|
class ReportListView(generics.ListAPIView):
|
||||||
@ -561,12 +561,12 @@ class BuildReportPrint(generics.RetrieveAPIView, BuildReportMixin, ReportPrintMi
|
|||||||
return self.print(request, builds)
|
return self.print(request, builds)
|
||||||
|
|
||||||
|
|
||||||
class POReportList(ReportListView, OrderReportMixin):
|
class PurchaseOrderReportList(ReportListView, OrderReportMixin):
|
||||||
|
|
||||||
OrderModel = order.models.PurchaseOrder
|
OrderModel = order.models.PurchaseOrder
|
||||||
|
|
||||||
queryset = PurchaseOrderReport.objects.all()
|
queryset = PurchaseOrderReport.objects.all()
|
||||||
serializer_class = POReportSerializer
|
serializer_class = PurchaseOrderReportSerializer
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
|
|
||||||
@ -618,16 +618,16 @@ class POReportList(ReportListView, OrderReportMixin):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class POReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
class PurchaseOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for a single PurchaseOrderReport object
|
API endpoint for a single PurchaseOrderReport object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = PurchaseOrderReport.objects.all()
|
queryset = PurchaseOrderReport.objects.all()
|
||||||
serializer_class = POReportSerializer
|
serializer_class = PurchaseOrderReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class POReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
class PurchaseOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
||||||
"""
|
"""
|
||||||
API endpoint for printing a PurchaseOrderReport object
|
API endpoint for printing a PurchaseOrderReport object
|
||||||
"""
|
"""
|
||||||
@ -635,7 +635,7 @@ class POReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin
|
|||||||
OrderModel = order.models.PurchaseOrder
|
OrderModel = order.models.PurchaseOrder
|
||||||
|
|
||||||
queryset = PurchaseOrderReport.objects.all()
|
queryset = PurchaseOrderReport.objects.all()
|
||||||
serializer_class = POReportSerializer
|
serializer_class = PurchaseOrderReportSerializer
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
||||||
@ -644,12 +644,12 @@ class POReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin
|
|||||||
return self.print(request, orders)
|
return self.print(request, orders)
|
||||||
|
|
||||||
|
|
||||||
class SOReportList(ReportListView, OrderReportMixin):
|
class SalesOrderReportList(ReportListView, OrderReportMixin):
|
||||||
|
|
||||||
OrderModel = order.models.SalesOrder
|
OrderModel = order.models.SalesOrder
|
||||||
|
|
||||||
queryset = SalesOrderReport.objects.all()
|
queryset = SalesOrderReport.objects.all()
|
||||||
serializer_class = SOReportSerializer
|
serializer_class = SalesOrderReportSerializer
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
def filter_queryset(self, queryset):
|
||||||
|
|
||||||
@ -701,16 +701,16 @@ class SOReportList(ReportListView, OrderReportMixin):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class SOReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
class SalesOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for a single SalesOrderReport object
|
API endpoint for a single SalesOrderReport object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = SalesOrderReport.objects.all()
|
queryset = SalesOrderReport.objects.all()
|
||||||
serializer_class = SOReportSerializer
|
serializer_class = SalesOrderReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class SOReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
class SalesOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
||||||
"""
|
"""
|
||||||
API endpoint for printing a PurchaseOrderReport object
|
API endpoint for printing a PurchaseOrderReport object
|
||||||
"""
|
"""
|
||||||
@ -718,7 +718,7 @@ class SOReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin
|
|||||||
OrderModel = order.models.SalesOrder
|
OrderModel = order.models.SalesOrder
|
||||||
|
|
||||||
queryset = SalesOrderReport.objects.all()
|
queryset = SalesOrderReport.objects.all()
|
||||||
serializer_class = SOReportSerializer
|
serializer_class = SalesOrderReportSerializer
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
||||||
@ -733,23 +733,23 @@ report_api_urls = [
|
|||||||
url(r'po/', include([
|
url(r'po/', include([
|
||||||
# Detail views
|
# Detail views
|
||||||
url(r'^(?P<pk>\d+)/', include([
|
url(r'^(?P<pk>\d+)/', include([
|
||||||
url(r'print/', POReportPrint.as_view(), name='api-po-report-print'),
|
url(r'print/', PurchaseOrderReportPrint.as_view(), name='api-po-report-print'),
|
||||||
url(r'^$', POReportDetail.as_view(), name='api-po-report-detail'),
|
url(r'^$', PurchaseOrderReportDetail.as_view(), name='api-po-report-detail'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# List view
|
# List view
|
||||||
url(r'^$', POReportList.as_view(), name='api-po-report-list'),
|
url(r'^$', PurchaseOrderReportList.as_view(), name='api-po-report-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# Sales order reports
|
# Sales order reports
|
||||||
url(r'so/', include([
|
url(r'so/', include([
|
||||||
# Detail views
|
# Detail views
|
||||||
url(r'^(?P<pk>\d+)/', include([
|
url(r'^(?P<pk>\d+)/', include([
|
||||||
url(r'print/', SOReportPrint.as_view(), name='api-so-report-print'),
|
url(r'print/', SalesOrderReportPrint.as_view(), name='api-so-report-print'),
|
||||||
url(r'^$', SOReportDetail.as_view(), name='api-so-report-detail'),
|
url(r'^$', SalesOrderReportDetail.as_view(), name='api-so-report-detail'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
url(r'^$', SOReportList.as_view(), name='api-so-report-list'),
|
url(r'^$', SalesOrderReportList.as_view(), name='api-so-report-list'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# Build reports
|
# Build reports
|
||||||
|
@ -466,6 +466,7 @@ class PurchaseOrderReport(ReportTemplateBase):
|
|||||||
return {
|
return {
|
||||||
'description': order.description,
|
'description': order.description,
|
||||||
'lines': order.lines,
|
'lines': order.lines,
|
||||||
|
'extra_lines': order.extra_lines,
|
||||||
'order': order,
|
'order': order,
|
||||||
'reference': order.reference,
|
'reference': order.reference,
|
||||||
'supplier': order.supplier,
|
'supplier': order.supplier,
|
||||||
@ -505,6 +506,7 @@ class SalesOrderReport(ReportTemplateBase):
|
|||||||
'customer': order.customer,
|
'customer': order.customer,
|
||||||
'description': order.description,
|
'description': order.description,
|
||||||
'lines': order.lines,
|
'lines': order.lines,
|
||||||
|
'extra_lines': order.extra_lines,
|
||||||
'order': order,
|
'order': order,
|
||||||
'prefix': common.models.InvenTreeSetting.get_setting('SALESORDER_REFERENCE_PREFIX'),
|
'prefix': common.models.InvenTreeSetting.get_setting('SALESORDER_REFERENCE_PREFIX'),
|
||||||
'reference': order.reference,
|
'reference': order.reference,
|
||||||
|
@ -58,7 +58,7 @@ class BOMReportSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class POReportSerializer(InvenTreeModelSerializer):
|
class PurchaseOrderReportSerializer(InvenTreeModelSerializer):
|
||||||
|
|
||||||
template = InvenTreeAttachmentSerializerField(required=True)
|
template = InvenTreeAttachmentSerializerField(required=True)
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ class POReportSerializer(InvenTreeModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SOReportSerializer(InvenTreeModelSerializer):
|
class SalesOrderReportSerializer(InvenTreeModelSerializer):
|
||||||
|
|
||||||
template = InvenTreeAttachmentSerializerField(required=True)
|
template = InvenTreeAttachmentSerializerField(required=True)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ from InvenTree.filters import InvenTreeOrderingFilter
|
|||||||
|
|
||||||
from order.models import PurchaseOrder
|
from order.models import PurchaseOrder
|
||||||
from order.models import SalesOrder, SalesOrderAllocation
|
from order.models import SalesOrder, SalesOrderAllocation
|
||||||
from order.serializers import POSerializer
|
from order.serializers import PurchaseOrderSerializer
|
||||||
|
|
||||||
from part.models import BomItem, Part, PartCategory
|
from part.models import BomItem, Part, PartCategory
|
||||||
from part.serializers import PartBriefSerializer
|
from part.serializers import PartBriefSerializer
|
||||||
@ -1315,7 +1315,7 @@ class StockTrackingList(generics.ListAPIView):
|
|||||||
if 'purchaseorder' in deltas:
|
if 'purchaseorder' in deltas:
|
||||||
try:
|
try:
|
||||||
order = PurchaseOrder.objects.get(pk=deltas['purchaseorder'])
|
order = PurchaseOrder.objects.get(pk=deltas['purchaseorder'])
|
||||||
serializer = POSerializer(order)
|
serializer = PurchaseOrderSerializer(order)
|
||||||
deltas['purchaseorder_detail'] = serializer.data
|
deltas['purchaseorder_detail'] = serializer.data
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
@ -165,6 +165,7 @@
|
|||||||
|
|
||||||
<script type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
|
||||||
<script type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
|
||||||
|
<script type='text/javascript' src="{% static 'script/qr-scanner.umd.min.js' %}"></script>
|
||||||
|
|
||||||
<!-- general JS -->
|
<!-- general JS -->
|
||||||
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
|
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
linkBarcodeDialog,
|
linkBarcodeDialog,
|
||||||
scanItemsIntoLocation,
|
scanItemsIntoLocation,
|
||||||
unlinkBarcode,
|
unlinkBarcode,
|
||||||
|
onBarcodeScanClicked,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function makeBarcodeInput(placeholderText='', hintText='') {
|
function makeBarcodeInput(placeholderText='', hintText='') {
|
||||||
@ -31,6 +32,9 @@ function makeBarcodeInput(placeholderText='', hintText='') {
|
|||||||
hintText = hintText || '{% trans "Enter barcode data" %}';
|
hintText = hintText || '{% trans "Enter barcode data" %}';
|
||||||
|
|
||||||
var html = `
|
var html = `
|
||||||
|
<div id='barcode_scan_video_container' class='text-center' style='height: 240px; display: none;'>
|
||||||
|
<video id='barcode_scan_video' disablepictureinpicture playsinline height='240' style='object-fit: fill;'></video>
|
||||||
|
</div>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label class='control-label' for='barcode'>{% trans "Barcode" %}</label>
|
<label class='control-label' for='barcode'>{% trans "Barcode" %}</label>
|
||||||
<div class='controls'>
|
<div class='controls'>
|
||||||
@ -39,6 +43,7 @@ function makeBarcodeInput(placeholderText='', hintText='') {
|
|||||||
<span class='fas fa-qrcode'></span>
|
<span class='fas fa-qrcode'></span>
|
||||||
</span>
|
</span>
|
||||||
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'>
|
<input id='barcode' class='textinput textInput form-control' type='text' name='barcode' placeholder='${placeholderText}'>
|
||||||
|
<button id='barcode_scan_btn' class='btn btn-secondary' onclick='onBarcodeScanClicked()' style='display: none;'><span class='fas fa-camera'></span></button>
|
||||||
</div>
|
</div>
|
||||||
<div id='hint_barcode_data' class='help-block'>${hintText}</div>
|
<div id='hint_barcode_data' class='help-block'>${hintText}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,6 +53,44 @@ function makeBarcodeInput(placeholderText='', hintText='') {
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qrScanner = null;
|
||||||
|
|
||||||
|
function startQrScanner() {
|
||||||
|
$('#barcode_scan_video_container').show();
|
||||||
|
qrScanner.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopQrScanner() {
|
||||||
|
if (qrScanner != null) qrScanner.stop();
|
||||||
|
$('#barcode_scan_video_container').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBarcodeScanClicked(e) {
|
||||||
|
if ($('#barcode_scan_video_container').is(':visible') == false) startQrScanner(); else stopQrScanner();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCameraAvailable(hasCamera, options) {
|
||||||
|
if ( hasCamera == true ) {
|
||||||
|
// Camera is only acccessible if page is served over secure connection
|
||||||
|
if ( window.isSecureContext == true ) {
|
||||||
|
qrScanner = new QrScanner(document.getElementById('barcode_scan_video'), (result) => {
|
||||||
|
onBarcodeScanCompleted(result, options);
|
||||||
|
}, {
|
||||||
|
highlightScanRegion: true,
|
||||||
|
highlightCodeOutline: true,
|
||||||
|
});
|
||||||
|
$('#barcode_scan_btn').show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBarcodeScanCompleted(result, options) {
|
||||||
|
if (result.data == '') return;
|
||||||
|
console.log('decoded qr code:', result.data);
|
||||||
|
stopQrScanner();
|
||||||
|
postBarcodeData(result.data, options);
|
||||||
|
}
|
||||||
|
|
||||||
function makeNotesField(options={}) {
|
function makeNotesField(options={}) {
|
||||||
|
|
||||||
var tooltip = options.tooltip || '{% trans "Enter optional notes for stock transfer" %}';
|
var tooltip = options.tooltip || '{% trans "Enter optional notes for stock transfer" %}';
|
||||||
@ -186,6 +229,11 @@ function barcodeDialog(title, options={}) {
|
|||||||
$(modal).on('shown.bs.modal', function() {
|
$(modal).on('shown.bs.modal', function() {
|
||||||
$(modal + ' .modal-form-content').scrollTop(0);
|
$(modal + ' .modal-form-content').scrollTop(0);
|
||||||
|
|
||||||
|
// Check for qr-scanner camera
|
||||||
|
QrScanner.hasCamera().then( (hasCamera) => {
|
||||||
|
onCameraAvailable(hasCamera, options);
|
||||||
|
});
|
||||||
|
|
||||||
var barcode = $(modal + ' #barcode');
|
var barcode = $(modal + ' #barcode');
|
||||||
|
|
||||||
// Handle 'enter' key on barcode
|
// Handle 'enter' key on barcode
|
||||||
@ -220,6 +268,12 @@ function barcodeDialog(title, options={}) {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(modal).on('hidden.bs.modal', function() {
|
||||||
|
stopQrScanner();
|
||||||
|
if (qrScanner != null) qrScanner.destroy();
|
||||||
|
qrScanner = null;
|
||||||
|
});
|
||||||
|
|
||||||
modalSetTitle(modal, title);
|
modalSetTitle(modal, title);
|
||||||
|
|
||||||
if (options.onSubmit) {
|
if (options.onSubmit) {
|
||||||
|
@ -26,15 +26,19 @@
|
|||||||
editPurchaseOrderLineItem,
|
editPurchaseOrderLineItem,
|
||||||
exportOrder,
|
exportOrder,
|
||||||
loadPurchaseOrderLineItemTable,
|
loadPurchaseOrderLineItemTable,
|
||||||
|
loadPurchaseOrderExtraLineTable
|
||||||
loadPurchaseOrderTable,
|
loadPurchaseOrderTable,
|
||||||
loadSalesOrderAllocationTable,
|
loadSalesOrderAllocationTable,
|
||||||
loadSalesOrderLineItemTable,
|
loadSalesOrderLineItemTable,
|
||||||
|
loadSalesOrderExtraLineTable
|
||||||
loadSalesOrderShipmentTable,
|
loadSalesOrderShipmentTable,
|
||||||
loadSalesOrderTable,
|
loadSalesOrderTable,
|
||||||
newPurchaseOrderFromOrderWizard,
|
newPurchaseOrderFromOrderWizard,
|
||||||
newSupplierPartFromOrderWizard,
|
newSupplierPartFromOrderWizard,
|
||||||
removeOrderRowFromOrderWizard,
|
removeOrderRowFromOrderWizard,
|
||||||
removePurchaseOrderLineItem,
|
removePurchaseOrderLineItem,
|
||||||
|
loadOrderTotal,
|
||||||
|
extraLineFields,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@ -272,7 +276,7 @@ function createPurchaseOrder(options={}) {
|
|||||||
if (options.onSuccess) {
|
if (options.onSuccess) {
|
||||||
options.onSuccess(data);
|
options.onSuccess(data);
|
||||||
} else {
|
} else {
|
||||||
// Default action is to redirect browser to the new PO
|
// Default action is to redirect browser to the new PurchaseOrder
|
||||||
location.href = `/order/purchase-order/${data.pk}/`;
|
location.href = `/order/purchase-order/${data.pk}/`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -305,6 +309,28 @@ function soLineItemFields(options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Construct a set of fields for a OrderExtraLine form */
|
||||||
|
function extraLineFields(options={}) {
|
||||||
|
|
||||||
|
var fields = {
|
||||||
|
order: {
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
quantity: {},
|
||||||
|
reference: {},
|
||||||
|
price: {},
|
||||||
|
price_currency: {},
|
||||||
|
notes: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.order) {
|
||||||
|
fields.order.value = options.order;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Construct a set of fields for the PurchaseOrderLineItem form */
|
/* Construct a set of fields for the PurchaseOrderLineItem form */
|
||||||
function poLineItemFields(options={}) {
|
function poLineItemFields(options={}) {
|
||||||
|
|
||||||
@ -502,7 +528,7 @@ function newPurchaseOrderFromOrderWizard(e) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Receive stock items against a PurchaseOrder
|
* Receive stock items against a PurchaseOrder
|
||||||
* Uses the POReceive API endpoint
|
* Uses the PurchaseOrderReceive API endpoint
|
||||||
*
|
*
|
||||||
* arguments:
|
* arguments:
|
||||||
* - order_id, ID / PK for the PurchaseOrder instance
|
* - order_id, ID / PK for the PurchaseOrder instance
|
||||||
@ -1373,6 +1399,226 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a table displaying lines for a particular PurchaseOrder
|
||||||
|
*
|
||||||
|
* @param {String} table : HTML ID tag e.g. '#table'
|
||||||
|
* @param {Object} options : object which contains:
|
||||||
|
* - order {integer} : pk of the PurchaseOrder
|
||||||
|
* - status: {integer} : status code for the order
|
||||||
|
*/
|
||||||
|
function loadPurchaseOrderExtraLineTable(table, options={}) {
|
||||||
|
|
||||||
|
options.table = table;
|
||||||
|
|
||||||
|
options.params = options.params || {};
|
||||||
|
|
||||||
|
if (!options.order) {
|
||||||
|
console.log('ERROR: function called without order ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.status) {
|
||||||
|
console.log('ERROR: function called without order status');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.params.order = options.order;
|
||||||
|
options.params.part_detail = true;
|
||||||
|
options.params.allocations = true;
|
||||||
|
|
||||||
|
var filters = loadTableFilters('purchaseorderextraline');
|
||||||
|
|
||||||
|
for (var key in options.params) {
|
||||||
|
filters[key] = options.params[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
options.url = options.url || '{% url "api-po-extra-line-list" %}';
|
||||||
|
|
||||||
|
var filter_target = options.filter_target || '#filter-list-purchase-order-extra-lines';
|
||||||
|
|
||||||
|
setupFilterList('purchaseorderextraline', $(table), filter_target);
|
||||||
|
|
||||||
|
// Is the order pending?
|
||||||
|
var pending = options.status == {{ SalesOrderStatus.PENDING }};
|
||||||
|
|
||||||
|
// Table columns to display
|
||||||
|
var columns = [
|
||||||
|
{
|
||||||
|
sortable: true,
|
||||||
|
field: 'reference',
|
||||||
|
title: '{% trans "Reference" %}',
|
||||||
|
switchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sortable: true,
|
||||||
|
field: 'quantity',
|
||||||
|
title: '{% trans "Quantity" %}',
|
||||||
|
footerFormatter: function(data) {
|
||||||
|
return data.map(function(row) {
|
||||||
|
return +row['quantity'];
|
||||||
|
}).reduce(function(sum, i) {
|
||||||
|
return sum + i;
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
switchable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sortable: true,
|
||||||
|
field: 'price',
|
||||||
|
title: '{% trans "Unit Price" %}',
|
||||||
|
formatter: function(value, row) {
|
||||||
|
var formatter = new Intl.NumberFormat(
|
||||||
|
'en-US',
|
||||||
|
{
|
||||||
|
style: 'currency',
|
||||||
|
currency: row.price_currency
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return formatter.format(row.price);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'total_price',
|
||||||
|
sortable: true,
|
||||||
|
title: '{% trans "Total Price" %}',
|
||||||
|
formatter: function(value, row) {
|
||||||
|
var formatter = new Intl.NumberFormat(
|
||||||
|
'en-US',
|
||||||
|
{
|
||||||
|
style: 'currency',
|
||||||
|
currency: row.price_currency
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return formatter.format(row.price * row.quantity);
|
||||||
|
},
|
||||||
|
footerFormatter: function(data) {
|
||||||
|
var total = data.map(function(row) {
|
||||||
|
return +row['price'] * row['quantity'];
|
||||||
|
}).reduce(function(sum, i) {
|
||||||
|
return sum + i;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
var currency = (data.slice(-1)[0] && data.slice(-1)[0].price_currency) || 'USD';
|
||||||
|
|
||||||
|
var formatter = new Intl.NumberFormat(
|
||||||
|
'en-US',
|
||||||
|
{
|
||||||
|
style: 'currency',
|
||||||
|
currency: currency
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return formatter.format(total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
field: 'notes',
|
||||||
|
title: '{% trans "Notes" %}',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pending) {
|
||||||
|
columns.push({
|
||||||
|
field: 'buttons',
|
||||||
|
switchable: false,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
|
var html = `<div class='btn-group float-right' role='group'>`;
|
||||||
|
|
||||||
|
var pk = row.pk;
|
||||||
|
|
||||||
|
html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
|
||||||
|
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
|
||||||
|
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadTable() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
reloadTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure callback functions once the table is loaded
|
||||||
|
function setupCallbacks() {
|
||||||
|
|
||||||
|
// Callback for duplicating lines
|
||||||
|
$(table).find('.button-duplicate').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
inventreeGet(`/api/order/po-extra-line/${pk}/`, {}, {
|
||||||
|
success: function(data) {
|
||||||
|
|
||||||
|
var fields = extraLineFields();
|
||||||
|
|
||||||
|
constructForm('{% url "api-po-extra-line-list" %}', {
|
||||||
|
method: 'POST',
|
||||||
|
fields: fields,
|
||||||
|
data: data,
|
||||||
|
title: '{% trans "Duplicate Line" %}',
|
||||||
|
onSuccess: function(response) {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Callback for editing lines
|
||||||
|
$(table).find('.button-edit').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
constructForm(`/api/order/po-extra-line/${pk}/`, {
|
||||||
|
fields: {
|
||||||
|
quantity: {},
|
||||||
|
reference: {},
|
||||||
|
price: {},
|
||||||
|
price_currency: {},
|
||||||
|
notes: {},
|
||||||
|
},
|
||||||
|
title: '{% trans "Edit Line" %}',
|
||||||
|
onSuccess: reloadTable,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Callback for deleting lines
|
||||||
|
$(table).find('.button-delete').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
constructForm(`/api/order/po-extra-line/${pk}/`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
title: '{% trans "Delete Line" %}',
|
||||||
|
onSuccess: reloadTable,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(table).inventreeTable({
|
||||||
|
onPostBody: setupCallbacks,
|
||||||
|
name: 'purchaseorderextraline',
|
||||||
|
sidePagination: 'client',
|
||||||
|
formatNoMatches: function() {
|
||||||
|
return '{% trans "No matching line" %}';
|
||||||
|
},
|
||||||
|
queryParams: filters,
|
||||||
|
original: options.params,
|
||||||
|
url: options.url,
|
||||||
|
showFooter: true,
|
||||||
|
uniqueId: 'pk',
|
||||||
|
detailViewByClick: false,
|
||||||
|
columns: columns,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load table displaying list of sales orders
|
* Load table displaying list of sales orders
|
||||||
*/
|
*/
|
||||||
@ -2259,6 +2505,26 @@ function showFulfilledSubTable(index, row, element, options) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var TotalPriceRef = ''; // reference to total price field
|
||||||
|
var TotalPriceOptions = {}; // options to reload the price
|
||||||
|
|
||||||
|
function loadOrderTotal(reference, options={}) {
|
||||||
|
TotalPriceRef = reference;
|
||||||
|
TotalPriceOptions = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadTotal() {
|
||||||
|
inventreeGet(
|
||||||
|
TotalPriceOptions.url,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
success: function(data) {
|
||||||
|
$(TotalPriceRef).html(data.total_price_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a table displaying line items for a particular SalesOrder
|
* Load a table displaying line items for a particular SalesOrder
|
||||||
@ -2556,6 +2822,7 @@ function loadSalesOrderLineItemTable(table, options={}) {
|
|||||||
|
|
||||||
function reloadTable() {
|
function reloadTable() {
|
||||||
$(table).bootstrapTable('refresh');
|
$(table).bootstrapTable('refresh');
|
||||||
|
reloadTotal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure callback functions once the table is loaded
|
// Configure callback functions once the table is loaded
|
||||||
@ -2765,3 +3032,223 @@ function loadSalesOrderLineItemTable(table, options={}) {
|
|||||||
columns: columns,
|
columns: columns,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a table displaying lines for a particular SalesOrder
|
||||||
|
*
|
||||||
|
* @param {String} table : HTML ID tag e.g. '#table'
|
||||||
|
* @param {Object} options : object which contains:
|
||||||
|
* - order {integer} : pk of the SalesOrder
|
||||||
|
* - status: {integer} : status code for the order
|
||||||
|
*/
|
||||||
|
function loadSalesOrderExtraLineTable(table, options={}) {
|
||||||
|
|
||||||
|
options.table = table;
|
||||||
|
|
||||||
|
options.params = options.params || {};
|
||||||
|
|
||||||
|
if (!options.order) {
|
||||||
|
console.log('ERROR: function called without order ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.status) {
|
||||||
|
console.log('ERROR: function called without order status');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.params.order = options.order;
|
||||||
|
options.params.part_detail = true;
|
||||||
|
options.params.allocations = true;
|
||||||
|
|
||||||
|
var filters = loadTableFilters('salesorderextraline');
|
||||||
|
|
||||||
|
for (var key in options.params) {
|
||||||
|
filters[key] = options.params[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
options.url = options.url || '{% url "api-so-extra-line-list" %}';
|
||||||
|
|
||||||
|
var filter_target = options.filter_target || '#filter-list-sales-order-extra-lines';
|
||||||
|
|
||||||
|
setupFilterList('salesorderextraline', $(table), filter_target);
|
||||||
|
|
||||||
|
// Is the order pending?
|
||||||
|
var pending = options.status == {{ SalesOrderStatus.PENDING }};
|
||||||
|
|
||||||
|
// Table columns to display
|
||||||
|
var columns = [
|
||||||
|
{
|
||||||
|
sortable: true,
|
||||||
|
field: 'reference',
|
||||||
|
title: '{% trans "Reference" %}',
|
||||||
|
switchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sortable: true,
|
||||||
|
field: 'quantity',
|
||||||
|
title: '{% trans "Quantity" %}',
|
||||||
|
footerFormatter: function(data) {
|
||||||
|
return data.map(function(row) {
|
||||||
|
return +row['quantity'];
|
||||||
|
}).reduce(function(sum, i) {
|
||||||
|
return sum + i;
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
switchable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sortable: true,
|
||||||
|
field: 'price',
|
||||||
|
title: '{% trans "Unit Price" %}',
|
||||||
|
formatter: function(value, row) {
|
||||||
|
var formatter = new Intl.NumberFormat(
|
||||||
|
'en-US',
|
||||||
|
{
|
||||||
|
style: 'currency',
|
||||||
|
currency: row.price_currency
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return formatter.format(row.price);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'total_price',
|
||||||
|
sortable: true,
|
||||||
|
title: '{% trans "Total Price" %}',
|
||||||
|
formatter: function(value, row) {
|
||||||
|
var formatter = new Intl.NumberFormat(
|
||||||
|
'en-US',
|
||||||
|
{
|
||||||
|
style: 'currency',
|
||||||
|
currency: row.price_currency
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return formatter.format(row.price * row.quantity);
|
||||||
|
},
|
||||||
|
footerFormatter: function(data) {
|
||||||
|
var total = data.map(function(row) {
|
||||||
|
return +row['price'] * row['quantity'];
|
||||||
|
}).reduce(function(sum, i) {
|
||||||
|
return sum + i;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
var currency = (data.slice(-1)[0] && data.slice(-1)[0].price_currency) || 'USD';
|
||||||
|
|
||||||
|
var formatter = new Intl.NumberFormat(
|
||||||
|
'en-US',
|
||||||
|
{
|
||||||
|
style: 'currency',
|
||||||
|
currency: currency
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return formatter.format(total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
field: 'notes',
|
||||||
|
title: '{% trans "Notes" %}',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pending) {
|
||||||
|
columns.push({
|
||||||
|
field: 'buttons',
|
||||||
|
switchable: false,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
|
var html = `<div class='btn-group float-right' role='group'>`;
|
||||||
|
|
||||||
|
var pk = row.pk;
|
||||||
|
|
||||||
|
html += makeIconButton('fa-clone', 'button-duplicate', pk, '{% trans "Duplicate line" %}');
|
||||||
|
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line" %}');
|
||||||
|
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line" %}', );
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadTable() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
reloadTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure callback functions once the table is loaded
|
||||||
|
function setupCallbacks() {
|
||||||
|
|
||||||
|
// Callback for duplicating lines
|
||||||
|
$(table).find('.button-duplicate').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
inventreeGet(`/api/order/so-extra-line/${pk}/`, {}, {
|
||||||
|
success: function(data) {
|
||||||
|
|
||||||
|
var fields = extraLineFields();
|
||||||
|
|
||||||
|
constructForm('{% url "api-so-extra-line-list" %}', {
|
||||||
|
method: 'POST',
|
||||||
|
fields: fields,
|
||||||
|
data: data,
|
||||||
|
title: '{% trans "Duplicate Line" %}',
|
||||||
|
onSuccess: function(response) {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Callback for editing lines
|
||||||
|
$(table).find('.button-edit').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
constructForm(`/api/order/so-extra-line/${pk}/`, {
|
||||||
|
fields: {
|
||||||
|
quantity: {},
|
||||||
|
reference: {},
|
||||||
|
price: {},
|
||||||
|
price_currency: {},
|
||||||
|
notes: {},
|
||||||
|
},
|
||||||
|
title: '{% trans "Edit Line" %}',
|
||||||
|
onSuccess: reloadTable,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Callback for deleting lines
|
||||||
|
$(table).find('.button-delete').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
constructForm(`/api/order/so-extra-line/${pk}/`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
title: '{% trans "Delete Line" %}',
|
||||||
|
onSuccess: reloadTable,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(table).inventreeTable({
|
||||||
|
onPostBody: setupCallbacks,
|
||||||
|
name: 'salesorderextraline',
|
||||||
|
sidePagination: 'client',
|
||||||
|
formatNoMatches: function() {
|
||||||
|
return '{% trans "No matching lines" %}';
|
||||||
|
},
|
||||||
|
queryParams: filters,
|
||||||
|
original: options.params,
|
||||||
|
url: options.url,
|
||||||
|
showFooter: true,
|
||||||
|
uniqueId: 'pk',
|
||||||
|
detailViewByClick: false,
|
||||||
|
columns: columns,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -271,7 +271,7 @@ function printBomReports(parts) {
|
|||||||
|
|
||||||
function printPurchaseOrderReports(orders) {
|
function printPurchaseOrderReports(orders) {
|
||||||
/**
|
/**
|
||||||
* Print PO reports for the provided purchase order(s)
|
* Print PurchaseOrder reports for the provided purchase order(s)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (orders.length == 0) {
|
if (orders.length == 0) {
|
||||||
@ -325,7 +325,7 @@ function printPurchaseOrderReports(orders) {
|
|||||||
|
|
||||||
function printSalesOrderReports(orders) {
|
function printSalesOrderReports(orders) {
|
||||||
/**
|
/**
|
||||||
* Print SO reports for the provided purchase order(s)
|
* Print SalesOrder reports for the provided purchase order(s)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (orders.length == 0) {
|
if (orders.length == 0) {
|
||||||
|
@ -132,6 +132,7 @@ class RuleSet(models.Model):
|
|||||||
'order_purchaseorder',
|
'order_purchaseorder',
|
||||||
'order_purchaseorderattachment',
|
'order_purchaseorderattachment',
|
||||||
'order_purchaseorderlineitem',
|
'order_purchaseorderlineitem',
|
||||||
|
'order_purchaseorderextraline',
|
||||||
'company_supplierpart',
|
'company_supplierpart',
|
||||||
'company_manufacturerpart',
|
'company_manufacturerpart',
|
||||||
'company_manufacturerpartparameter',
|
'company_manufacturerpartparameter',
|
||||||
@ -142,6 +143,7 @@ class RuleSet(models.Model):
|
|||||||
'order_salesorderallocation',
|
'order_salesorderallocation',
|
||||||
'order_salesorderattachment',
|
'order_salesorderattachment',
|
||||||
'order_salesorderlineitem',
|
'order_salesorderlineitem',
|
||||||
|
'order_salesorderextraline',
|
||||||
'order_salesordershipment',
|
'order_salesordershipment',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ django-mptt==0.11.0 # Modified Preorder Tree Traversal
|
|||||||
django-redis>=5.0.0 # Redis integration
|
django-redis>=5.0.0 # Redis integration
|
||||||
django-q==1.3.4 # Background task scheduling
|
django-q==1.3.4 # Background task scheduling
|
||||||
django-sql-utils==0.5.0 # Advanced query annotation / aggregation
|
django-sql-utils==0.5.0 # Advanced query annotation / aggregation
|
||||||
|
django-sslserver==0.22 # Secure HTTP development server
|
||||||
django-stdimage==5.1.1 # Advanced ImageField management
|
django-stdimage==5.1.1 # Advanced ImageField management
|
||||||
django-test-migrations==1.1.0 # Unit testing for database migrations
|
django-test-migrations==1.1.0 # Unit testing for database migrations
|
||||||
django-user-sessions==1.7.1 # user sessions in DB
|
django-user-sessions==1.7.1 # user sessions in DB
|
||||||
|
Loading…
Reference in New Issue
Block a user