Skip to content

Commit 4bcc8ec

Browse files
committed
10 Feb 2024 - Added purchase order
1) Added purchase order and purchase order items tables. 2) Added PURCHASE_STAT setting. 3) Added company name, address, telephone, email settings. 4) Include company info in DO & PO. 5) PAGE-settings.php - Company address will be the odd textarea. 6) Some general bug fix and code cleanup for delivery order. 7) Updated autocomplete - Added purchase supplier suggestion and fixed supplier item to also include supplier id.
1 parent 3ea1341 commit 4bcc8ec

29 files changed

+985
-115
lines changed

TODO.txt

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
11
STORAGE BOXX TO DO / WISH LIST
22
==============================
3-
* Add PO
3+
4+
PRIORITY
5+
========
6+
*) Edit/Delete item > deal with cascade effects
7+
*) Edit/Delete customer > deal with cascade effects
8+
*) Edit/Delete supplier > deal with cascade effects
9+
10+
11+
GOOD TO HAVE
12+
============
13+
* Check Item update, add More Info
14+
* Suppliers
15+
* Purchases + Projected In/Out
16+
* Items list update
17+
* Remove Suppliers from dropdown options
18+
* Rename "movement" to "check" (replace suppliers with check)
419
* Links
520
* Suppliers > PO
621
* Customers > DO
722
* Items > Suppliers > Purchase
8-
* Check Item > Add More Info > Suppliers + Purchase + Projected In/Out >
923
* Auto-create PO for all items low on stock
1024
* Home page widgets
11-
* Settings - add company info - Use in PO/DO/Invoice/documents
1225
* Directly email PO/DO
13-
* PO - sortable items.
14-
* LIB-Items > function save > also update PO items.
1526
* More reports - delivery/sales, purchases.
16-
* Print QR/NFC after import items
27+
* Print QR/NFC after import items.
28+
* Customer "address book"? Different branches/departments - For DO.
1729
* Add tutorial videos.

assets/PAGE-deliver.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,17 @@ var dlv = {
9797
let n = document.getElementById("cus-name"),
9898
i = document.getElementById("cus-id"),
9999
c = document.getElementById("cus-change");
100-
// @TODO
100+
101+
// (E2) RESET CUSTOMER
101102
if (reset) {
102103
n.value = "";
103104
n.disabled = false;
104105
i.value = "";
105106
c.classList.add("d-none");
106-
} else {
107+
}
108+
109+
// (E3) SET CUSTOMER
110+
else {
107111
n.disabled = true;
108112
c.classList.remove("d-none");
109113
}

assets/PAGE-purchase.js

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
var pur = {
2+
// (A) SHOW ALL PURCHASES
3+
pg : 1, // current page
4+
find : "", // current search
5+
list : silent => {
6+
if (silent!==true) { cb.page(1); }
7+
cb.load({
8+
page : "purchase/list", target : "pur-list",
9+
data : {
10+
page : pur.pg,
11+
search : pur.find
12+
}
13+
});
14+
},
15+
16+
// (B) GO TO PAGE
17+
// pg : page number
18+
goToPage : pg => { if (pg!=pur.pg) {
19+
pur.pg = pg;
20+
pur.list();
21+
}},
22+
23+
// (C) SEARCH PURCHASES
24+
search : () => {
25+
pur.find = document.getElementById("pur-search").value;
26+
pur.pg = 1;
27+
pur.list();
28+
return false;
29+
},
30+
31+
// (D) SHOW ADD/EDIT DOCKET
32+
// id : purchase id, for edit only
33+
iList : null, // current items for the order
34+
addEdit : id => cb.load({
35+
page : "purchase/form", target : "cb-page-2",
36+
data : { id : id ? id : "" },
37+
onload : () => {
38+
// (D1) RESET ITEMS LIST
39+
pur.iList = {};
40+
41+
// (D2) DRAW ITEMS
42+
if (id) {
43+
let hitems = document.getElementById("pur-items-data"),
44+
items = JSON.parse(hitems.innerHTML);
45+
hitems.remove();
46+
items.forEach(i => pur.addItem(...i));
47+
}
48+
49+
// (D3) ATTACH SUPPLIER AUTOCOMPLETE
50+
var hsupname = document.getElementById("sup-name");
51+
if (!hsupname.disabled) {
52+
autocomplete.attach({
53+
target : hsupname,
54+
mod : "autocomplete", act : "sup",
55+
data : { more : 1 },
56+
onpick : sup => {
57+
hsupname.value = sup.n;
58+
sup.v = JSON.parse(sup.v);
59+
document.getElementById("sup-id").value = sup.v.i;
60+
pur.csup(false);
61+
}
62+
});
63+
}
64+
65+
// (D4) ATTACH ADD ITEM AUTOCOMPLETE
66+
autocomplete.attach({
67+
target : document.getElementById("add-item"),
68+
mod : "autocomplete", act : "supitem",
69+
data : {
70+
more : 1,
71+
sid : document.getElementById("sup-id")
72+
},
73+
onpick : item => {
74+
document.getElementById("add-item").value = "";
75+
item = JSON.parse(item.v);
76+
pur.addItem(item.s, item.ss, item.n, item.u, item.p, 1);
77+
}
78+
});
79+
80+
// (D5) ATTACH NFC ADD ITEM
81+
if ("NDEFReader" in window) {
82+
nfc.init(pur.addGet);
83+
if (id) { document.getElementById("nfc-btn").disabled = false; }
84+
}
85+
86+
// (D6) SHOW PURCHASE ORDER PAGE
87+
cb.page(2);
88+
}
89+
}),
90+
91+
// (E) TOGGLE SUPPLIER CHANGE
92+
csup : reset => {
93+
// (E1) GET HTML ELEMENTS
94+
let n = document.getElementById("sup-name"),
95+
i = document.getElementById("sup-id"),
96+
c = document.getElementById("sup-change"),
97+
it = document.getElementById("pur-items"),
98+
ait = document.getElementById("add-item"),
99+
aqr = document.getElementById("qr-btn"),
100+
anfc = document.getElementById("nfc-btn");
101+
102+
// (E2) RESET SUPPLIER
103+
if (reset) {
104+
n.value = "";
105+
n.disabled = false;
106+
i.value = "";
107+
c.classList.add("d-none");
108+
pur.iList = {};
109+
it.innerHTML = "";
110+
ait.disabled = true;
111+
aqr.disabled = true;
112+
if ("NDEFReader" in window) { anfc.disabled = true; }
113+
}
114+
115+
// (E3) SET SUPPLIER
116+
else {
117+
n.disabled = true;
118+
c.classList.remove("d-none");
119+
ait.disabled = false;
120+
aqr.disabled = false;
121+
if ("NDEFReader" in window) { anfc.disabled = false; }
122+
}
123+
},
124+
125+
// (F) ADD ITEM ROW
126+
addItem : (sku, ssku, name, unit, price, qty) => {
127+
// (F1) CHECK DUPLICATE ITEM
128+
if (pur.iList[sku]) {
129+
cb.modal("Already Added", `[${sku}] ${name} is already added.`);
130+
}
131+
132+
// (F2) ADD NEW ROW
133+
else {
134+
// (F2-1) ITEM ROW HTML
135+
let row = document.createElement("div");
136+
row.className = "iRow d-flex align-items-center border p-2";
137+
row.innerHTML =
138+
`<i class="text-danger icon-cross p-3" onclick="pur.delItem(this, '${sku}')"></i>
139+
<div class="flex-grow-1">
140+
<strong class="iSKU">${sku}</strong>
141+
<div class="iName">${name}</div>
142+
</div>
143+
<div class="form-floating">
144+
<input class="form-control mx-1 iQty" type="number" step="0.01" min="0.01" required value="${qty}">
145+
<label class="iUnit">${unit}</label>
146+
</div>
147+
<div class="form-floating">
148+
<input class="form-control iPrice" type="number" step="0.01" min="0" required value="${price}">
149+
<label>PRICE</label>
150+
</div>`;
151+
152+
// (F2-2) SORTABLE
153+
row.draggable = true;
154+
row.ondragstart = () => pur.ddfrom = row;
155+
row.ondragover = e => e.preventDefault();
156+
row.ondrop = pur.isort;
157+
158+
// (F2-3) APPEND TO LIST
159+
document.getElementById("pur-items").appendChild(row);
160+
pur.iList[sku] = 1;
161+
}
162+
},
163+
164+
// (G) DRAG-N-DROP SORT ITEM
165+
ddfrom : null, // current item being dragged
166+
ddto : null, // dropped at this item
167+
ddget : r => r.classList.contains("iRow") ? r : pur.ddget(r.parentElement), // get proper drop target
168+
isort : e => {
169+
// (G1) GET ELEMENTS
170+
e.preventDefault();
171+
let iList = document.getElementById("pur-items"),
172+
iAll = iList.querySelectorAll(".iRow");
173+
pur.ddto = pur.ddget(e.target);
174+
175+
// (G2) REORDER ITEM
176+
if (iAll.length>1 && pur.ddfrom!=pur.ddto) {
177+
let currentpos = 0, droppedpos = 0;
178+
for (let i=0; i<iAll.length; i++) {
179+
if (pur.ddfrom == iAll[i]) { currentpos = i; }
180+
if (pur.ddto == iAll[i]) { droppedpos = i; }
181+
}
182+
if (currentpos < droppedpos) {
183+
iList.insertBefore(pur.ddfrom, pur.ddto.nextSibling);
184+
} else {
185+
iList.insertBefore(pur.ddfrom, pur.ddto);
186+
}
187+
}
188+
},
189+
190+
// (H) REMOVE ITEM ROW
191+
delItem : (row, sku) => {
192+
row.parentElement.remove();
193+
delete pur.iList[sku];
194+
},
195+
196+
// (I) GET ITEM FROM SERVER & ADD TO LIST
197+
addGet : sku => cb.api({
198+
mod : "suppliers", act : "getItem",
199+
data : {
200+
id : document.getElementById("sup-id").value,
201+
sku : sku
202+
},
203+
passmsg : false,
204+
onpass : res => {
205+
// (I1) INVALID SKU
206+
if (res.data==null) {
207+
cb.modal("Invalid Item", `${sku} is not found in the database, or supplier does not have this item.`);
208+
}
209+
210+
// (I2) OK - ADD ITEM
211+
else {
212+
let i = res.data;
213+
pur.addItem(i.item_sku, i.sup_sku, i.item_name, i.item_unit, i.unit_price, 1);
214+
}
215+
}
216+
}),
217+
218+
// (J) ADD ITEM WITH QR CODE
219+
addQR : () => {
220+
if (qrscan.scanner==null) { qrscan.init(pur.addGet); }
221+
qrscan.show();
222+
},
223+
224+
// (K) SAVE PURCHASE
225+
save : () => {
226+
// (K1) GET DATA
227+
var data = {
228+
sid : document.getElementById("sup-id").value,
229+
name : document.getElementById("p-name").value,
230+
tel : document.getElementById("p-tel").value,
231+
email : document.getElementById("p-email").value,
232+
address : document.getElementById("p-address").value,
233+
date : document.getElementById("p-date").value,
234+
notes : document.getElementById("p-notes").value
235+
};
236+
if (data.sid=="") {
237+
cb.modal("No supplier specified", "Please select a supplier.");
238+
return false;
239+
}
240+
var id = document.getElementById("p-id").value;
241+
if (id!="") {
242+
data.id = id;
243+
data.stat = document.getElementById("p-stat").value;
244+
}
245+
246+
// (K2) GET ITEMS
247+
let items = document.querySelectorAll("#pur-items .iRow");
248+
if (items.length==0) {
249+
cb.modal("No Items", "Please add at least one item.");
250+
return false;
251+
}
252+
data.items = [];
253+
// sku | price | qty
254+
for (let i of items) {
255+
data.items.push([
256+
i.querySelector(".iSKU").innerHTML,
257+
i.querySelector(".iPrice").value,
258+
i.querySelector(".iQty").value
259+
]);
260+
}
261+
data.items = JSON.stringify(data.items);
262+
263+
// (K3) AJAX
264+
cb.api({
265+
mod : "purchase", act : "save",
266+
data : data,
267+
passmsg : "Order saved",
268+
onpass : pur.list
269+
});
270+
return false;
271+
},
272+
273+
// (L) PRINT PURCHASE ORDER
274+
print : id => {
275+
document.getElementById("pur-print-id").value = id;
276+
document.getElementById("pur-print").submit();
277+
}
278+
};
279+
280+
// (M) INIT MANAGE PURCHASES
281+
window.addEventListener("load", () => {
282+
// (M1) EXTRA STYLES FOR "ADD/EDIT ITEMS LIST"
283+
document.head.appendChild(document.createElement("style")).innerHTML=".iQty,.iPrice{width:80px}";
284+
285+
// (M2) LIST PURCHASES
286+
pur.list();
287+
288+
// (M3) ATTACH AUTOCOMPLETE
289+
autocomplete.attach({
290+
target : document.getElementById("pur-search"),
291+
mod : "autocomplete", act : "purchase",
292+
onpick : res => pur.search()
293+
});
294+
});

assets/PAGE-settings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
function save () {
22
// (A) GET ALL DATA
33
let data = {};
4-
for (let i of document.querySelectorAll("#set-list input[type=text]")) {
4+
for (let i of document.querySelectorAll("#set-list .form-control")) {
55
data[i.name] = i.value;
66
}
77

assets/PAGE-sup-items.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var items = {
1616
});
1717
},
1818

19-
// (B) LIST () : SHOW SUPPLIER ITEMS
19+
// (B) LIST : SHOW SUPPLIER ITEMS
2020
list : silent => {
2121
if (silent!==true) { cb.page(2); }
2222
cb.load({
@@ -29,7 +29,8 @@ var items = {
2929
onload : () => autocomplete.attach({
3030
target : document.getElementById("item-search"),
3131
mod : "autocomplete", act : "supitem",
32-
onpick : res => items.search()
32+
data : { sid : items.id },
33+
onpick : items.search
3334
})
3435
});
3536
},

0 commit comments

Comments
 (0)