Freeman Software Technologies logo
FREEMAN SOFTWARE TECHNOLOGIESSmart Solutions for a Brighter TomorrowConceptualised, Designed and Developed by Patrick Freeman Kudiabor
Home
Freeman Logo

FREEMAN PROCUREMENT & INVENTORY

Purchase Orders • Goods Received • Supplier Invoices • Payments • GHS
Development application: records are stored only in this browser. There is no authentication, immutable audit trail, server synchronisation or tax calculation. All amounts are exclusive of tax. Do not use for live purchasing approvals or statutory accounts.
Purchase orders0
Goods received (units)0
Supplier liabilities (GHS)0.00
Unpaid invoices0

1. Register supplier

2. Create purchase order

Create the product in Retail first. A purchase order does not change stock or the General Ledger.

3. Receive ordered goods

Goods receipt increases the retail product quantity and records a GRN. Partial deliveries are supported.

4. Register supplier invoice

Invoice amount is calculated from received quantity × purchase order unit cost. Invoice posting debits Inventory and credits Accounts Payable; it does not duplicate the stock receipt.

5. Supplier payment voucher

Payments debit Accounts Payable and credit the selected cash or bank account. No partial payments in this release.

6. Document register

TypeReferenceSupplier / SKUQuantityAmount (GHS)Status

7. Control and reconciliation

For backup of retail quantities, also export a separate Retail "use strict";const PK='freeman-procurement-v17',RK='freeman-retail-v15';const $=id=>document.getElementById(id);const now=()=>new Date().toISOString();const uid=prefix=>prefix+'-'+Date.now().toString(36)+'-'+Math.random().toString(36).slice(2,7);const amt=n=>Math.round(Number(n)*100);const fmt=c=>(c/100).toLocaleString('en-GB',{minimumFractionDigits:2,maximumFractionDigits:2});const fresh=()=>({suppliers:[],pos:[],grns:[],invoices:[],payments:[]});function parse(key,fallback){try{return JSON.parse(localStorage.getItem(key))||fallback}catch{return fallback}}let db=parse(PK,fresh());if(!db||!['suppliers','pos','grns','invoices','payments'].every(k=>Array.isArray(db[k])))db=fresh();let retail=parse(RK,{products:[],sales:[],moves:[]});function reloadRetail(){retail=parse(RK,{products:[],sales:[],moves:[]});if(!Array.isArray(retail.products))retail={products:[],sales:[],moves:[]}}function persist(){localStorage.setItem(PK,JSON.stringify(db));render()}function error(e){alert(e.message||String(e))}function guard(fn){return e=>{e.preventDefault();try{fn(new FormData(e.currentTarget));e.currentTarget.reset();persist()}catch(ex){error(ex)}}}function required(cond,msg){if(!cond)throw Error(msg)}function select(id,arr,format){const s=$(id),v=s.value;s.replaceChildren();const z=document.createElement('option');z.value='';z.textContent='Select…';s.append(z);arr.forEach(x=>{const o=document.createElement('option');o.value=x.id||x.sku;o.textContent=format(x);s.append(o)});if([...s.options].some(o=>o.value===v))s.value=v}function row(vals){let tr=document.createElement('tr');vals.forEach(x=>{let td=document.createElement('td');td.textContent=String(x);tr.append(td)});$('register').append(tr)}function render(){reloadRetail();select('poSupplier',db.suppliers,s=>s.name);select('poSku',retail.products,p=>p.sku+' — '+p.name);select('grnPo',db.pos.filter(p=>p.receivedp.id+' | '+p.sku+' | outstanding '+(p.qty-p.received));select('invoiceGrn',db.grns.filter(g=>!db.invoices.some(i=>i.grn===g.id)),g=>g.id+' | '+g.sku+' | '+fmt(g.total));select('payInvoice',db.invoices.filter(i=>!db.payments.some(p=>p.invoice===i.id)),i=>i.id+' | '+fmt(i.total));$('kpo').textContent=db.pos.length;$('kgrn').textContent=db.grns.reduce((s,g)=>s+g.qty,0);let total=db.invoices.reduce((s,i)=>s+i.total,0),paid=db.payments.reduce((s,p)=>s+p.total,0);$('kap').textContent=fmt(total-paid);$('kunpaid').textContent=db.invoices.filter(i=>!db.payments.some(p=>p.invoice===i.id)).length;$('register').replaceChildren();db.pos.forEach(p=>row(['PO',p.id,p.supplier+' / '+p.sku,p.qty,fmt(p.qty*p.unit),p.received===p.qty?'Received':'Outstanding '+(p.qty-p.received)]));db.grns.forEach(g=>row(['GRN',g.id,g.sku,g.qty,fmt(g.total),'Received']));db.invoices.forEach(i=>row(['INVOICE',i.ref,i.supplier,i.qty,fmt(i.total),db.payments.some(p=>p.invoice===i.id)?'Paid':'Unpaid']));db.payments.forEach(p=>row(['PV',p.ref,p.method,'—',fmt(p.total),'Recorded']));$('control').textContent='Invoice total: GHS '+fmt(total)+' | Payments: GHS '+fmt(paid)+' | Outstanding accounts payable: GHS '+fmt(total-paid)+' | GRNs awaiting invoice: '+db.grns.filter(g=>!db.invoices.some(i=>i.grn===g.id)).length+'.'}$('supplierForm').addEventListener('submit',guard(f=>{let name=String(f.get('name')).trim();required(name,'Supplier name required');db.suppliers.push({id:uid('SUP'),name,contact:String(f.get('contact')),date:now()})}));$('poForm').addEventListener('submit',guard(f=>{let supplier=db.suppliers.find(s=>s.id===f.get('supplier')),sku=String(f.get('sku')),qty=Number(f.get('qty')),unit=amt(f.get('unit'));required(supplier&&retail.products.some(p=>p.sku===sku),'Select a supplier and product');required(Number.isSafeInteger(qty)&&qty>0&&qty<=100000&&Number.isSafeInteger(unit)&&unit>0,'Invalid quantity or cost');db.pos.push({id:uid('PO'),supplier:supplier.name,supplierId:supplier.id,sku,qty,unit,received:0,date:now()})}));$('grnForm').addEventListener('submit',guard(f=>{let po=db.pos.find(p=>p.id===f.get('po')),qty=Number(f.get('qty'));required(po&&Number.isSafeInteger(qty)&&qty>0&&qty<=po.qty-po.received,'Invalid receipt quantity');reloadRetail();let product=retail.products.find(p=>p.sku===po.sku);required(product,'Product missing in Retail');required(Number.isSafeInteger(product.qty)&&product.qty+qty<=100000000,'Invalid stock balance');let grn={id:uid('GRN'),po:po.id,sku:po.sku,supplier:po.supplier,qty,unit:po.unit,total:qty*po.unit,date:now()};required(Number.isSafeInteger(grn.total),'Amount too large');product.qty+=qty;retail.moves=Array.isArray(retail.moves)?retail.moves:[];retail.moves.push({id:grn.id,date:grn.date,sku:po.sku,type:'Procurement GRN',qty,reason:'Receipt against '+po.id});localStorage.setItem(RK,JSON.stringify(retail));po.received+=qty;db.grns.push(grn)}));$('invoiceForm').addEventListener('submit',guard(f=>{let g=db.grns.find(x=>x.id===f.get('grn')),ref=String(f.get('ref')).trim();required(g&&!db.invoices.some(i=>i.grn===g.id),'Select uninvoiced GRN');required(ref&&!db.invoices.some(i=>i.ref.toLowerCase()===ref.toLowerCase()),'Invoice reference must be unique');db.invoices.push({id:uid('INV'),grn:g.id,ref,supplier:g.supplier,qty:g.qty,total:g.total,date:now()})}));$('payForm').addEventListener('submit',guard(f=>{let i=db.invoices.find(x=>x.id===f.get('invoice')),ref=String(f.get('ref')).trim(),method=String(f.get('method'));required(i&&!db.payments.some(p=>p.invoice===i.id),'Select unpaid invoice');required(ref&&!db.payments.some(p=>p.ref.toLowerCase()===ref.toLowerCase()),'Voucher reference must be unique');required(['Bank','Cash','Mobile Money'].includes(method),'Invalid method');db.payments.push({id:uid('PAY'),invoice:i.id,ref,method,total:i.total,date:now()})}));$('export').addEventListener('click',()=>{let blob=new Blob([JSON.stringify({exportedAt:now(),procurement:db},null,2)],{type:'application/json'}),url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download='freeman-procurement-v17-backup.json';a.click();setTimeout(()=>URL.revokeObjectURL(url),2000)});$('print').addEventListener('click',()=>window.print());render();ON backup. No server-side authorisation is implemented.

Budget & Forecast ↗
Financial Statements ↗
Conceptualised, Designed and Developed by Patrick Freeman Kudiabor