Files
sap-erp/dist-1/public/Itemcreationform.html
T
John 69b4e68baf
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s
first commit
2026-09-23 17:31:02 +05:30

1499 lines
77 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/auth-guard.js?v=4"></script>
<script src="/company-list.js"></script>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<title>Item Master — SAP B1</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"/>
<script src="/lucide.min.js?v=399"></script>
<style>
:root{
--blue:#2563eb;--blue2:#1d4ed8;--blue-light:#eff6ff;
--purple:#7c3aed;
--text:#0f172a;--text2:#334155;--text3:#64748b;--text4:#94a3b8;
--bg:#f4f6f9;--ink2:#f8fafc;--surface:#fff;--surface2:#f8fafc;--surface3:#eef1f5;--border:#e2e8f0;--border2:#cbd5e1;
--font:'Space Grotesk',sans-serif;--mono:'JetBrains Mono',ui-monospace,monospace;
--red:#dc2626;--green:#16a34a;--amber:#d97706;--radius:10px;
--shadow:0 1px 3px rgba(15,23,42,.06),0 1px 2px rgba(15,23,42,.04);
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--font);background:var(--bg);color:var(--text);min-height:100vh;-webkit-font-smoothing:antialiased}
main{padding:24px;max-width:1250px;margin:0 auto}
.tabs{display:flex;gap:4px;margin-bottom:16px;background:var(--surface3);border-radius:10px;padding:4px}
.tab{flex:1;padding:9px;border-radius:8px;font-size:12px;font-weight:700;color:var(--text3);cursor:pointer;border:none;background:none;font-family:var(--font);text-align:center;transition:all .15s;white-space:nowrap}
.tab:hover{color:var(--text2)}
.tab.on{background:var(--surface);color:var(--blue);box-shadow:var(--shadow)}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);margin-bottom:16px;overflow:visible}
.card-hd{background:var(--surface2);padding:12px 18px;font-size:13px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--border);border-radius:10px 10px 0 0;flex-wrap:wrap}
.card-hd .ico{width:16px;height:16px;color:var(--blue);flex-shrink:0;display:inline-flex}
.card-hd .right{margin-left:auto;font-size:11px;color:var(--text3);font-weight:400}
.card-bd{padding:18px}
.fg{margin-bottom:12px}
.fl{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-bottom:5px;display:block}
.fi{width:100%;padding:8px 11px;border:1.5px solid var(--border);border-radius:8px;font-size:13px;font-family:var(--font);background:var(--surface);color:var(--text);outline:none;height:36px}.fi:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(37,99,235,.12)}.fi::placeholder{color:var(--text4)}
select.fi{cursor:pointer;appearance:none;background:var(--surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%2394a3b8'/%3E%3C/svg%3E") no-repeat right 10px center;padding-right:26px}
.fi-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.fi-row-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px}
@media(max-width:500px){.fi-row,.fi-row-3{grid-template-columns:1fr}}
.fi-mono{font-family:var(--mono);font-size:12px;text-transform:uppercase}
textarea.fi{height:auto;min-height:60px;resize:vertical;line-height:1.5;padding-top:8px}
.chk-row{display:flex;gap:16px;flex-wrap:wrap;margin-bottom:8px}
.chk-item{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12.5px;color:var(--text2);user-select:none}
.chk-item input[type=checkbox]{width:15px;height:15px;accent-color:var(--blue);cursor:pointer}
.chk-item input[type=radio]{width:15px;height:15px;accent-color:var(--blue);cursor:pointer}
.divider{border:none;border-top:1px solid var(--border);margin:12px 0}
.btn-bar{display:flex;gap:8px;flex-wrap:wrap}
.btn{padding:10px 20px;border-radius:9px;border:none;font-family:var(--font);font-size:13px;font-weight:700;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:6px;justify-content:center}.btn:disabled{opacity:.4;cursor:not-allowed}
.b-primary{background:var(--blue);color:#fff;flex:1}.b-primary:hover:not(:disabled){background:var(--blue2);box-shadow:0 4px 16px rgba(37,99,235,.35)}
.b-success{background:var(--green);color:#fff}.b-success:hover:not(:disabled){background:#15803d;box-shadow:0 4px 16px rgba(22,163,74,.3)}
.b-cancel{background:var(--surface2);color:var(--text2);border:1px solid var(--border2)}.b-cancel:hover{border-color:var(--text3)}
.b-danger{background:rgba(220,38,38,.06);color:var(--red);border:1px solid rgba(220,38,38,.2)}.b-danger:hover{background:rgba(220,38,38,.12)}
.b-view{background:var(--blue-light);color:var(--blue);border:1px solid #bfdbfe}.b-view:hover{background:#dbeafe}
.b-amber{background:rgba(217,119,6,.1);color:var(--amber);border:1px solid rgba(217,119,6,.3);font-size:12px;padding:7px 16px;border-radius:8px;font-weight:700;cursor:pointer;font-family:var(--font);transition:all .15s}.b-amber:hover:not(:disabled){background:rgba(217,119,6,.18)}.b-amber:disabled{opacity:.35;cursor:not-allowed}
.alert{padding:10px 12px;border-radius:8px;font-size:12px;margin-bottom:12px;line-height:1.5}
.al-green{background:rgba(22,163,74,.08);border:1px solid rgba(22,163,74,.2);color:#166534}
.al-red{background:rgba(220,38,38,.08);border:1px solid rgba(220,38,38,.2);color:#b91c1c}
.al-amber{background:rgba(217,119,6,.08);border:1px solid rgba(217,119,6,.2);color:#92400e}
.sp{width:14px;height:14px;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;display:inline-block}
@keyframes spin{to{transform:rotate(360deg)}}
.toast-c{position:fixed;top:16px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:6px;pointer-events:none}
.toast{padding:9px 13px;border-radius:8px;font-size:12px;font-weight:600;box-shadow:0 8px 28px rgba(15,23,42,.18);animation:sIn .22s ease;max-width:280px;pointer-events:auto}
.t-ok{background:var(--green);color:#fff}.t-err{background:var(--red);color:#fff}.t-warn{background:var(--amber);color:#fff}
@keyframes sIn{from{transform:translateX(110%);opacity:0}to{transform:translateX(0);opacity:1}}
.badge{display:inline-block;font-size:9px;font-weight:700;padding:2px 7px;border-radius:4px;text-transform:uppercase}
.badge-purple{background:rgba(124,58,237,.1);color:var(--purple);border:1px solid rgba(124,58,237,.25)}
.badge-green{background:rgba(22,163,74,.1);color:var(--green);border:1px solid rgba(22,163,74,.25)}
.badge-amber{background:rgba(217,119,6,.1);color:var(--amber);border:1px solid rgba(217,119,6,.25)}
.badge-blue{background:var(--blue-light);color:var(--blue);border:1px solid #bfdbfe}
.badge-red{background:rgba(220,38,38,.1);color:var(--red);border:1px solid rgba(220,38,38,.25)}
.sec-label{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--text3);margin-bottom:10px;padding-bottom:5px;border-bottom:1px solid var(--border)}
.stk-table{width:100%;border-collapse:collapse;font-size:12px;margin-top:8px}
.stk-table th{padding:8px;text-align:left;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;color:var(--text3);background:var(--surface3);border-bottom:1px solid var(--border)}
.stk-table td{padding:8px;border-bottom:1px solid var(--border);color:var(--text2);font-family:var(--mono)}
.stk-table tr.wh-row{cursor:pointer}
.stk-table tr.wh-row:hover td{background:var(--blue-light)}
.stk-table tr.wh-row.selected td{background:#dbeafe;color:var(--text)}
.stk-table tr.wh-row.is-default td{color:var(--text)}
.stk-table td.bold{font-weight:700;color:var(--text)}
.stk-table td.green{color:var(--green)}
.whse-btn-row{display:flex;justify-content:flex-end;margin-top:10px;gap:8px;align-items:center}
.whse-btn-row .default-label{font-size:11px;color:var(--text3);font-style:italic}
.tab-panel{display:none}.tab-panel.active{display:block}
.req{color:var(--red);margin-left:2px}
.cost-row{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:8px;margin-bottom:8px}
.cost-label{font-size:11px;color:var(--text3);font-weight:600;text-transform:uppercase;letter-spacing:.3px}
.cost-val{font-family:var(--mono);font-size:13px;color:var(--amber);font-weight:600}
.item-code-display{font-family:var(--mono);font-size:20px;color:var(--blue);font-weight:600;letter-spacing:1px}
.star-badge{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;background:rgba(217,119,6,.12);border:1px solid rgba(217,119,6,.35);border-radius:4px;color:var(--amber);margin-left:4px;vertical-align:middle}
/* === AUTO-NUMBERING STYLES === */
.code-prefix{font-family:var(--mono);font-size:16px;color:var(--blue);font-weight:600;letter-spacing:1px;background:var(--blue-light);padding:6px 12px;border-radius:7px;border:1px solid #bfdbfe;display:inline-flex;align-items:center;min-width:60px;justify-content:center}
.code-input-wrap{display:flex;align-items:center;gap:8px;flex:1}
.code-input-wrap .fi{flex:1}
.code-meta{display:flex;align-items:center;gap:10px;margin-top:6px;font-size:11px}
.code-sequence{color:var(--amber);font-family:var(--mono);font-weight:600}
.code-status{color:var(--text3);font-style:italic}
.btn-refresh{padding:6px 10px;border-radius:7px;font-size:10px;background:var(--surface2);border:1px solid var(--border2);color:var(--text2);cursor:pointer;font-family:var(--font);display:inline-flex;align-items:center;gap:4px}.btn-refresh:hover{border-color:var(--blue);color:var(--blue)}
.btn-refresh:disabled{opacity:.4;cursor:not-allowed}
.btn-refresh i{width:12px;height:12px}
.btn-manual{padding:6px 10px;border-radius:7px;font-size:10px;background:var(--surface2);border:1px solid var(--border2);color:var(--blue);cursor:pointer;font-family:var(--font)}.btn-manual:hover{border-color:var(--blue)}
.btn-manual.on{background:var(--blue-light);border-color:var(--blue);color:var(--blue)}
.code-row{display:flex;align-items:center;gap:10px;margin-bottom:6px;flex-wrap:wrap}
.code-manual-hint{font-size:10px;color:var(--text3);margin-top:4px;font-style:italic}
/* === ITEM LIST STYLES === */
.list-toolbar{display:flex;gap:8px;margin-bottom:14px;align-items:center;flex-wrap:wrap}
.search-box{flex:1;min-width:200px;position:relative}
.search-box input{width:100%;padding:9px 12px 9px 34px;border:1.5px solid var(--border);border-radius:8px;background:var(--surface);color:var(--text);font-family:var(--font);font-size:13px;outline:none}
.search-box input:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(37,99,235,.12)}
.search-box svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);width:14px;height:14px;color:var(--text4);pointer-events:none}
.filter-chip{padding:5px 11px;border-radius:7px;font-size:10.5px;font-weight:600;background:var(--surface2);border:1px solid var(--border2);color:var(--text2);cursor:pointer;font-family:var(--font)}
.filter-chip.on{background:var(--blue-light);color:var(--blue);border-color:#bfdbfe}
/* Item tile grid — same vocabulary/values as Production Order's po-grid/
po-card.tile, so the browse screens across both modules match exactly. */
.item-list{min-height:1px}
.po-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px}
.po-card{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:12px;cursor:pointer;transition:all .15s;box-shadow:var(--shadow)}
.po-card:hover{border-color:var(--blue);box-shadow:0 4px 16px rgba(37,99,235,.12);transform:translateY(-1px)}
.po-card.tile{aspect-ratio:1/1;display:flex;flex-direction:column;overflow:hidden;padding:12px}
.po-card.active{border-color:var(--blue);box-shadow:0 0 0 3px rgba(37,99,235,.12)}
.po-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:4px}
.po-code{font-family:var(--mono);font-size:11px;color:var(--blue);font-weight:700}
.po-badge{font-size:9px;font-weight:700;padding:2px 7px;border-radius:4px;text-transform:uppercase;flex-shrink:0}
.po-badge.active{background:rgba(22,163,74,.1);color:var(--green);border:1px solid rgba(22,163,74,.25)}
.po-badge.inactive{background:rgba(220,38,38,.1);color:var(--red);border:1px solid rgba(220,38,38,.25)}
.po-icon{width:28px;height:28px;border-radius:7px;background:var(--blue-light);color:var(--blue);display:flex;align-items:center;justify-content:center;margin:4px 0}
.po-icon svg{width:15px;height:15px}
.po-name{font-size:11.5px;line-height:1.3;color:var(--text);font-weight:600;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.po-badges{display:flex;gap:3px;flex-wrap:wrap;margin-top:4px}
.po-item-badge{font-size:9px;padding:1px 6px;border-radius:4px;background:var(--surface3);color:var(--text3);border:1px solid var(--border)}
.po-meta{margin-top:auto;padding-top:6px;border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;gap:6px}
.po-actions{display:flex;gap:4px}
.btn-icon{padding:4px 6px;border-radius:6px;background:var(--surface2);border:1px solid var(--border2);color:var(--text2);cursor:pointer;display:inline-flex;align-items:center;justify-content:center}.btn-icon:hover{color:var(--blue);border-color:var(--blue)}
.btn-icon svg{width:12px;height:12px}
.empty-state{text-align:center;padding:44px 20px;color:var(--text3);font-size:13px;grid-column:1/-1}
.empty-state .big{width:34px;height:34px;margin:0 auto 10px;display:block;color:var(--text4)}
.pagination{display:flex;justify-content:center;gap:8px;margin-top:14px;align-items:center}
.pagination button{padding:5px 12px;border-radius:7px;font-size:11px;background:var(--surface);border:1px solid var(--border2);color:var(--text2);cursor:pointer;font-family:var(--font)}.pagination button:disabled{opacity:.3;cursor:not-allowed}.pagination button:hover:not(:disabled){border-color:var(--blue);color:var(--blue)}
.pagination .page-info{font-size:11px;color:var(--text3)}
.mode-badge{position:fixed;top:16px;left:16px;z-index:100;padding:5px 11px;border-radius:7px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1px;background:var(--surface);border:1px solid var(--border);color:var(--text2);box-shadow:var(--shadow)}
.mode-badge.create{border-color:var(--green);color:var(--green)}
.mode-badge.edit{border-color:var(--amber);color:var(--amber)}
.mode-badge.view{border-color:var(--blue);color:var(--blue)}
.readonly-field{background:var(--surface3)!important;color:var(--text3)!important;cursor:not-allowed!important;border-color:var(--border)!important}
</style>
</head>
<body>
<div class="toast-c" id="toast-c"></div>
<div class="mode-badge create" id="mode-badge">Create Mode</div>
<nav></nav>
<main>
<div id="app"></div>
</main>
<script>
'use strict';
let _tok = null, _user = null;
const BACKEND = '/api';
(function(){try{_tok=sessionStorage.getItem('portal_token');const u=sessionStorage.getItem('portal_user');if(u)_user=JSON.parse(u);}catch{}})();
if(!_tok) window.location.href='/';
let _co = window.__DEFAULT_COMPANY__;
let _companies=[{value:_co,label:_co}]; // live list — populated in init()
function coOptions(sel){return _companies.map(c=>`<option value="${esc(c.value)}" ${sel===c.value?'selected':''}>${esc(c.label)}</option>`).join('');}
let _mode = new URLSearchParams(window.location.search).get('list')==='1' ? 'list' : 'create';
let _activeTab = 'general';
let _saving = false;
let _itemGroups = [], _uomGroups = [], _wh = [], _priceLists = [];
let _selectedWhRow = null;
let _currentItemCode = null;
let _itemList = [];
let _listSkip = 0;
let _listTop = 20;
let _listQ = '';
let _listTotal = 0;
const ITEM_TYPE_CONFIG = {
finished: { prefix: 'FG', label: 'Finished Goods', groupName: 'FINISH GOODS', materialType: 'mt_FinishedGoods', hasSeries: false, defaultManual: true, padLen: 6 },
rm: { prefix: 'RM', label: 'Raw Material', groupName: 'RAW MATERIAL', materialType: 'mt_RawMaterial', hasSeries: true, defaultManual: false, padLen: 5 },
pk: { prefix: 'PK', label: 'Packing Material', groupName: 'PACKING MATERIAL', materialType: 'mt_RawMaterial', hasSeries: true, defaultManual: false, padLen: 5 },
ico: { prefix: 'ICO', label: 'Consumable/ICO', groupName: 'PROD-CONSUMABLE', materialType: 'mt_RawMaterial', hasSeries: true, defaultManual: false, padLen: 5 },
custom: { prefix: '', label: 'Custom / Other', groupName: '', materialType: 'mt_FinishedGoods', hasSeries: false, defaultManual: true, padLen: 6 },
};
let _selectedTypeKey = 'finished';
let _lastSequence = null;
let _isManualMode = false;
let _sequenceError = null;
let _form = defaultForm();
function defaultForm(){
return {
ItemCode: '', ItemName: '', ForeignName: '',
ItemType: 'itItems',
ItemsGroupCode: '',
UoMGroupEntry: -1,
PriceList: 1,
InventoryItem: 'tYES', SalesItem: 'tYES', PurchaseItem: 'tYES',
ManageSerialNumbers: 'tNO',
ManageBatchNumbers: 'tNO',
SRIAndBatchManageMethod: 'bomm_OnEveryTransaction',
ItemCategory: 'Material',
ItemClass: 'itcMaterial',
MaterialType: 'mt_FinishedGoods',
ProductSource: '0',
GSTRelevant: 'tNO',
TaxCategory: '',
GSTTaxCategory: '',
Excisable: 'tNO',
Frozen: 'tNO',
PlanningSystem: 'bop_None', ProcurementMethod: 'bom_Buy',
ComponentWarehouse: 'bomcw_BOM',
OrderIntervals: null, OrderMultiple: 0, MinimumOrderQuantity: 0,
IssueMethod: 'im_Manual',
ProductionStandardCost: 0,
IncludeInStdCostRollup: 'tYES',
HsnCode: '',
InventoryUOM: '',
GLMethod: 'glm_ItemClass',
ValuationMethod: 'bis_MovingAverage',
UserText: '',
DefaultWarehouse: '',
AssessableValue: 0,
LeadTime: null,
PurchaseUnit: '', SalesUnit: '',
PurchaseQtyPerPackage: 0, PurchaseItemsPerUnit: 0,
SalesQtyPerPackage: 0, SalesMinimumOrderQuantity: 0,
WarrantyTemplate: '',
DefaultVendor: '',
LeadTimeDays: null,
WTLiable: 'tNO',
NoDiscounts: 'tNO',
TreeType: 'iNotATree',
AssetItem: 'tNO',
TaxType: 'tt_Yes',
Valid: 'tYES',
ManageStockByWarehouse: 'tNO',
InCostRollup: 'tYES',
DesiredInventory: 0,
MinimumInventory: 0,
MaximumQuantityInWarehouse: 0,
Weight1: 0,
ToleranceDays: 0,
ArTaxCode: '', ApTaxCode: '',
SalesRevenueAccount: '', PurchaseAccount: '',
ShippingType: '',
BarCode: '', AdditionalIdentifier: '',
};
}
function today(){return new Date().toISOString().slice(0,10);}
function esc(v){return(v||'').toString().replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
function q(s){return document.querySelector(s);}
async function api(m, p, b=null){
const h={'Content-Type':'application/json'};if(_tok)h['Authorization']='Bearer '+_tok;
const o={method:m.toUpperCase(),headers:h};if(b)o.body=JSON.stringify(b);
let r,d;try{r=await fetch(BACKEND+p,o);d=await r.json();}catch(e){throw new Error('Cannot reach server');}
if(r.status===401){window.location.href='/';throw new Error('Session expired');}
if(!r.ok)throw new Error(d?.message||'HTTP '+r.status);
return d;
}
function toast(msg, type='ok'){
const tc=document.getElementById('toast-c');
const el=document.createElement('div');
el.className=`toast t-${type}`;
el.textContent=(type==='ok'?'✓ ':type==='err'?'✗ ':'⚠ ')+msg;
tc.appendChild(el);setTimeout(()=>el.remove(),4000);
}
function normalizeWh(raw){
const code = raw.WarehouseCode || raw.WhouseCode || raw.warehouseCode || raw.WH_Code || raw.whCode || raw.Code || raw.code || '';
const name = raw.WarehouseName || raw.WhouseName || raw.warehouseName || raw.WH_Name || raw.whName || raw.Name || raw.name || '';
const branch = raw.Branch || raw.branch || raw.BranchName || '';
const locked = raw.Locked === 'tYES' || raw.Locked === true || raw.locked === true;
const onHand = raw.InStock || raw.OnHand || raw.onHand || raw.in_stock || 0;
const committed = raw.IsCommited || raw.Committed || raw.committed || raw.IsCommitted || 0;
const onOrder = raw.OnOrder || raw.onOrder || raw.on_order || 0;
return {code, name, branch, locked, onHand, committed, onOrder};
}
function _apiTimeout(ms){ return new Promise((_,r)=>setTimeout(()=>r(new Error('timeout')),ms)); }
async function loadLookups(){
try{
const [grpR, whR] = await Promise.all([
Promise.race([api('GET',`/sap/lookup/item-groups?company=${encodeURIComponent(_co)}`), _apiTimeout(5000)]).catch(()=>({data:[]})),
Promise.race([api('GET',`/sap/lookup/warehouses?company=${encodeURIComponent(_co)}`), _apiTimeout(5000)]).catch(()=>({data:[]}))
]);
_itemGroups = grpR.data||[];
const rawWh = whR.data||[];
_wh = rawWh.map(normalizeWh);
}catch(e){ console.error('[loadLookups]', e); }
}
const ITEM_GROUPS_DEMO = [
'FINISH GOODS','FINISH GOODS STENT','FINISH GOODS TT DEVICE',
'FIXED ASSETS','FURNITURE & FIXTURES','GENERAL','HOUSEKEEPING',
'IT ASSETS','IT CONSUMABLES','Items','MACHINE RAW MATERIAL',
'MACHINE SPARE PARTS','MACHINE TOOLS','MAINTENANCE','PACKING MATERIAL',
'PACKING MATERIAL BB EQ','PACKING MATERIAL STENT','PART',
'PRINTING & STATIONERY','PROD-CONSUMABLE','PRODUCTION TOOLS',
'RAW MATERIAL','RAW MATERIAL BB EQ','RAW MATERIAL CLIA',
'RAW MATERIAL STENT','SCRAP GOODS'
];
const WH_DEMO = [
{code:'01', name:'RAW MATERIAL', branch:'', locked:false, onHand:0, committed:0, onOrder:0},
{code:'02', name:'PACKING MATERIAL',branch:'', locked:false, onHand:0, committed:0, onOrder:0},
{code:'03', name:'REJECTION', branch:'', locked:false, onHand:0, committed:0, onOrder:0},
{code:'04', name:'QC SAMPLES', branch:'', locked:false, onHand:71, committed:0, onOrder:0},
{code:'05', name:'QA', branch:'', locked:false, onHand:40, committed:0, onOrder:0},
{code:'06', name:'CONSUMABLE', branch:'', locked:false, onHand:0, committed:0, onOrder:0},
];
const ITEM_TYPES = [{v:'itItems',l:'Items'},{v:'itLabor',l:'Labor'},{v:'itTravel',l:'Travel'},{v:'itFixedAssets',l:'Fixed Assets'},{v:'itService',l:'Service'}];
const ISSUE_METHODS = [{v:'im_Manual',l:'Manual'},{v:'im_Backflush',l:'Backflush'}];
const PLAN_SYSTEMS = [{v:'bop_None',l:'None'},{v:'bop_MRP',l:'MRP'}];
const PROC_METHODS = [{v:'bom_Buy',l:'Buy'},{v:'bom_Make',l:'Make'}];
const VALUATION_METHODS = [{v:'bis_MovingAverage',l:'Moving Average'},{v:'bis_FIFO',l:'FIFO'},{v:'bis_Standard',l:'Standard'}];
const TAX_CATEGORIES = [{v:'gtc_Regular',l:'Regular'},{v:'gtc_Exempt',l:'Exempt'},{v:'gtc_ZeroRated',l:'Zero Rated'}];
const MATERIAL_TYPES = [{v:'mt_FinishedGoods',l:'Finished Goods'},{v:'mt_RawMaterial',l:'Raw Material'},{v:'mt_SemiFinishedGoods',l:'Semi-Finished'}];
// ════════════════════════════════════════════════════════════════
// AUTO-NUMBERING ENGINE
// ════════════════════════════════════════════════════════════════
async function fetchLastSequence(prefix) {
_sequenceError = null;
if (!prefix) return 0;
try {
const res = await api('GET', `/sap/lookup/last-item-code?prefix=${encodeURIComponent(prefix)}&company=${encodeURIComponent(_co)}`);
if (res.success && res.lastNumber !== undefined && res.lastNumber !== null) {
const num = parseInt(res.lastNumber);
_lastSequence = isNaN(num) ? 0 : num;
return _lastSequence;
}
if (res.warning) _sequenceError = res.warning;
_lastSequence = 0;
return 0;
} catch (e) {
_sequenceError = e.message;
_lastSequence = 0;
return 0;
}
}
function generateNextCode(prefix, lastNum, padLen) {
const nextNum = (lastNum || 0) + 1;
const pad = (typeof padLen === 'number' && padLen > 0) ? padLen : 6;
const sequence = String(nextNum).padStart(pad, '0');
return `${prefix}${sequence}`;
}
async function refreshItemCode(forceManual) {
const config = ITEM_TYPE_CONFIG[_selectedTypeKey];
if (!config) return;
if (forceManual !== undefined) _isManualMode = forceManual;
else if (config.defaultManual) _isManualMode = true;
else _isManualMode = false;
if (_isManualMode || !config.hasSeries) {
_form.ItemCode = '';
_lastSequence = null;
render();
return;
}
await fetchLastSequence(config.prefix);
const nextCode = generateNextCode(config.prefix, _lastSequence, config.padLen);
_form.ItemCode = nextCode;
_form.MaterialType = config.materialType;
const matchingGroup = _itemGroups.find(g => (g.name || '').toUpperCase().includes(config.groupName));
if (matchingGroup) _form.ItemsGroupCode = matchingGroup.code;
render();
toast(`Auto-generated: ${nextCode}`, 'ok');
}
function toggleManualMode() {
_isManualMode = !_isManualMode;
if (_isManualMode) {
_form.ItemCode = '';
_lastSequence = null;
_sequenceError = null;
render();
} else {
refreshItemCode(false);
}
}
// ════════════════════════════════════════════════════════════════
// NAVIGATION HELPERS
// ════════════════════════════════════════════════════════════════
function goToList() {
_mode = 'list';
_currentItemCode = null;
const badge = q('#mode-badge');
if (badge) { badge.className = 'mode-badge'; badge.textContent = 'Browse Mode'; }
// Clear app so toolbar rebuilds from scratch
const app = q('#app');
if(app) app.innerHTML = '';
loadItemList();
}
// ════════════════════════════════════════════════════════════════
// MODE SWITCHING
// ════════════════════════════════════════════════════════════════
function setMode(mode, itemCode=null){
_mode = mode;
_currentItemCode = itemCode;
const badge = q('#mode-badge');
if(badge){
badge.className = `mode-badge ${mode}`;
badge.textContent = mode==='create'?'Create Mode':mode==='edit'?'Edit Mode':'View Mode';
}
if(mode==='create'){
_form = defaultForm();
_selectedWhRow = null;
_selectedTypeKey = 'finished';
_lastSequence = null;
_isManualMode = false;
_sequenceError = null;
_activeTab = 'general';
render();
} else if(itemCode){
loadItemForEdit(itemCode, mode==='view');
}
}
async function loadItemForEdit(itemCode, readonly=false){
try{
toast('Loading item...','warn');
const res = await api('GET', `/sap/items/${encodeURIComponent(itemCode)}/detail?company=${encodeURIComponent(_co)}`);
if(!res.success) throw new Error(res.message||'Item not found');
const d = res.data;
_form = {
...defaultForm(),
ItemCode: d.ItemCode||'',
ItemName: d.ItemName||'',
ForeignName: d.ForeignName||'',
ItemType: d.ItemType||'itItems',
ItemsGroupCode: d.ItemsGroupCode!=null?String(d.ItemsGroupCode):'',
UoMGroupEntry: d.UoMGroupEntry||-1,
InventoryItem: d.InventoryItem||'tYES',
SalesItem: d.SalesItem||'tYES',
PurchaseItem: d.PurchaseItem||'tYES',
ManageSerialNumbers: d.ManageSerialNumbers||'tNO',
ManageBatchNumbers: d.ManageBatchNumbers||'tNO',
SRIAndBatchManageMethod: d.SRIAndBatchManageMethod||'bomm_OnEveryTransaction',
ItemCategory: d.ItemClass==='itcService'?'Service':'Material',
ItemClass: d.ItemClass||'itcMaterial',
MaterialType: d.MaterialType||'mt_FinishedGoods',
ProductSource: d.ProductSource||'0',
GSTRelevant: d.GSTRelevnt||d.GSTRelevant||'tNO',
GSTTaxCategory: d.GSTTaxCategory||'',
TaxCategory: d.GSTTaxCategory||'',
Excisable: d.Excisable||'tNO',
Frozen: d.Frozen||'tNO',
PlanningSystem: d.PlanningSystem||'bop_None',
ProcurementMethod: d.ProcurementMethod||'bom_Buy',
ComponentWarehouse: d.ComponentWarehouse||'bomcw_BOM',
IssueMethod: d.IssueMethod||'im_Manual',
ProductionStandardCost: d.ProdStdCost||0,
IncludeInStdCostRollup: d.InCostRollup||'tYES',
HsnCode: d.ChapterID!=null?String(d.ChapterID):'',
InventoryUOM: d.InventoryUOM||'',
GLMethod: d.GLMethod||'glm_ItemClass',
ValuationMethod: d.CostAccountingMethod||'bis_MovingAverage',
UserText: d.User_Text||d.UserText||'',
DefaultWarehouse: d.DefaultWarehouse||'',
AssessableValue: d.AssessableValue||0,
LeadTime: d.LeadTime||null,
PurchaseUnit: d.PurchaseUnit||'',
SalesUnit: d.SalesUnit||'',
PurchaseQtyPerPackage: d.PurchaseQtyPerPackUnit||0,
PurchaseItemsPerUnit: d.PurchaseItemsPerUnit||0,
SalesQtyPerPackage: d.SalesQtyPerPackUnit||0,
SalesMinimumOrderQuantity: d.MinInventory||0,
WarrantyTemplate: d.WarrantyTemplate||'',
DefaultVendor: d.Mainsupplier||'',
LeadTimeDays: d.LeadTime||null,
WTLiable: d.WTLiable||'tNO',
NoDiscounts: d.NoDiscounts||'tNO',
TreeType: d.TreeType||'iNotATree',
AssetItem: d.AssetItem||'tNO',
TaxType: d.TaxType||'tt_Yes',
Valid: d.Valid||'tYES',
ManageStockByWarehouse: d.ManageStockByWarehouse||'tNO',
InCostRollup: d.InCostRollup||'tYES',
DesiredInventory: d.DesiredInventory||0,
MinimumInventory: d.MinInventory||0,
MaximumQuantityInWarehouse: d.MaxInventory||0,
Weight1: d.InventoryWeight||0,
ToleranceDays: d.ToleranceDays||0,
ArTaxCode: d.ArTaxCode||'',
ApTaxCode: d.ApTaxCode||'',
SalesRevenueAccount: d.IncomeAccount||'',
PurchaseAccount: d.ExpanseAccount||'',
ShippingType: d.ShipType!=null?String(d.ShipType):'',
BarCode: d.BarCode||'',
AdditionalIdentifier: d.SWW||'',
};
const grpName = (_itemGroups.find(g=>String(g.code)===String(d.ItemsGroupCode))||{}).name||'';
if(grpName.includes('RAW')) _selectedTypeKey='rm';
else if(grpName.includes('PACK')) _selectedTypeKey='pk';
else if(grpName.includes('CONSUMABLE')) _selectedTypeKey='ico';
else if(grpName.includes('FINISH')) _selectedTypeKey='finished';
else _selectedTypeKey='custom';
_selectedWhRow = d.DefaultWarehouse||null;
_activeTab = 'general';
render();
toast(`Loaded: ${itemCode}`, 'ok');
}catch(e){
toast(e.message,'err');
goToList();
}
}
// ════════════════════════════════════════════════════════════════
// ITEM LIST
// ════════════════════════════════════════════════════════════════
async function loadItemList(){
try{
const res = await api('GET', `/sap/items?company=${encodeURIComponent(_co)}&q=${encodeURIComponent(_listQ)}&skip=${_listSkip}&top=${_listTop}`);
_itemList = res.data||[];
_listTotal = res.count||0;
renderList();
// Restore focus to search box after render, preserving cursor
const searchEl = q('#list-search');
if(searchEl) {
const len = searchEl.value.length;
searchEl.focus();
searchEl.setSelectionRange(len, len);
}
}catch(e){
toast('Failed to load items','err');
}
}
function renderList(){
const app = q('#app');
// Only build the card shell once — if it already exists, just update the list
if(!q('#list-toolbar-root')){
app.innerHTML = '';
const c = document.createElement('div');
c.className = 'card';
c.innerHTML = `<div class="card-hd"><span class="ico"><i data-lucide="package" style="width:16px;height:16px"></i></span> Item Master Data
<span class="right" style="display:flex;align-items:center;gap:8px">
<select class="fi" id="sel-list-co" style="height:28px;font-size:11px;width:160px;padding:2px 22px 2px 8px">
${coOptions(_co)}
</select>
</span>
</div>
<div class="card-bd">
<div class="list-toolbar" id="list-toolbar-root">
<div class="search-box">
<i data-lucide="search" style="width:14px;height:14px"></i>
<input type="text" id="list-search" placeholder="Search item code or name..."
value="${esc(_listQ)}" autocomplete="off" spellcheck="false"/>
</div>
<button class="btn b-success" id="btn-new-item" style="padding:8px 16px;font-size:12px"><i data-lucide="plus" style="width:14px;height:14px"></i> New Item</button>
</div>
<div id="list-container" class="item-list"></div>
<div id="list-pagination" class="pagination"></div>
</div>`;
app.appendChild(c);
if(window.lucide&&lucide.createIcons)lucide.createIcons();
q('#sel-list-co').onchange = function(){_co=this.value;_listSkip=0;loadItemList();};
q('#list-search').oninput = function(e){
_listQ = e.target.value;
_listSkip = 0;
clearTimeout(window._searchDebounce);
window._searchDebounce = setTimeout(loadItemList, 400);
};
q('#btn-new-item').onclick = ()=>setMode('create');
}
renderListItems();
renderListPagination();
}
function renderListItems(){
const listContainer = q('#list-container');
if(!listContainer) return;
if(!_itemList.length){
listContainer.innerHTML = `<div class="po-grid"><div class="empty-state"><i data-lucide="package-search" class="big"></i>No items found</div></div>`;
if(window.lucide&&lucide.createIcons)lucide.createIcons();
return;
}
listContainer.innerHTML = `<div class="po-grid">${_itemList.map(item=>{
const isActive = item.ItemCode === _currentItemCode;
const statusCls = item.Frozen==='tYES' ? 'inactive' : 'active';
const statusLbl = item.Frozen==='tYES' ? 'Inactive' : 'Active';
return `<div class="po-card tile${isActive?' active':''}" data-code="${esc(item.ItemCode)}">
<div class="po-top"><span class="po-code">${esc(item.ItemCode)}</span><span class="po-badge ${statusCls}">${statusLbl}</span></div>
<div class="po-icon"><i data-lucide="package"></i></div>
<div class="po-name" title="${esc(item.ItemName||'')}">${esc(item.ItemName||'—')}</div>
<div class="po-badges">
<span class="po-item-badge">${esc(item.InventoryUOM||'PCS')}</span>
<span class="po-item-badge">${esc(item.DefaultWarehouse||'—')}</span>
</div>
<div class="po-meta">
<span class="po-actions">
<button class="btn-icon" data-action="view" data-code="${esc(item.ItemCode)}" title="View"><i data-lucide="eye"></i></button>
<button class="btn-icon" data-action="edit" data-code="${esc(item.ItemCode)}" title="Edit"><i data-lucide="pencil"></i></button>
</span>
</div>
</div>`;
}).join('')}</div>`;
listContainer.querySelectorAll('.po-card').forEach(card=>{
card.onclick = ()=>setMode('view', card.dataset.code);
});
listContainer.querySelectorAll('[data-action]').forEach(btn=>{
btn.onclick = (e)=>{
e.stopPropagation();
const code = btn.dataset.code;
const action = btn.dataset.action;
setMode(action, code);
};
});
if(window.lucide&&lucide.createIcons)lucide.createIcons();
}
function renderListPagination(){
const pagination = q('#list-pagination');
if(!pagination) return;
const hasMore = _itemList.length === _listTop;
pagination.innerHTML = `
<button ${_listSkip===0?'disabled':''} id="btn-prev">← Prev</button>
<span class="page-info">${_listSkip+1} – ${_listSkip+_itemList.length}</span>
<button ${!hasMore?'disabled':''} id="btn-next">Next →</button>
`;
q('#btn-prev').onclick = ()=>{ if(_listSkip>=_listTop){_listSkip-=_listTop;loadItemList();}};
q('#btn-next').onclick = ()=>{ if(hasMore){_listSkip+=_listTop;loadItemList();}};
}
// ════════════════════════════════════════════════════════════════
// MAIN RENDER
// ════════════════════════════════════════════════════════════════
function render(){
const app = q('#app');
if(!app) return;
// FIX: only redirect to list if mode is explicitly 'list'
if(_mode === 'list'){
loadItemList();
return;
}
app.innerHTML = '';
// Back button bar for edit/view
if(_mode !== 'create'){
const backBar = document.createElement('div');
backBar.style.cssText = 'margin-bottom:12px;display:flex;gap:8px;align-items:center';
backBar.innerHTML = `
<button class="btn b-cancel" id="btn-back-list-top"><i data-lucide="arrow-left" style="width:14px;height:14px"></i> Back to List</button>
<span style="font-size:12px;color:var(--text3)">Item: <strong style="color:var(--text);font-family:var(--mono)">${esc(_form.ItemCode)}</strong></span>
`;
app.appendChild(backBar);
q('#btn-back-list-top').onclick = goToList;
}
const config = ITEM_TYPE_CONFIG[_selectedTypeKey] || ITEM_TYPE_CONFIG.finished;
const isAuto = !(_isManualMode || !config.hasSeries);
const prefixDisplay = config.prefix || '—';
const isReadonly = _mode === 'view';
const hdr = document.createElement('div');
hdr.className = 'card';
hdr.innerHTML = `<div class="card-hd"><span class="ico"><i data-lucide="id-card" style="width:16px;height:16px"></i></span> Item Master Data
<span class="right" style="display:flex;align-items:center;gap:8px">
<select class="fi" id="sel-co" style="height:28px;font-size:11px;width:160px;padding:2px 22px 2px 8px">
${coOptions(_co)}
</select>
${_mode==='edit'?'<span class="badge badge-amber">Editing</span>':''}
${_mode==='view'?'<span class="badge badge-blue">View Only</span>':''}
</span>
</div>
<div class="card-bd">
<div class="fi-row">
<div class="fg">
<label class="fl">Item Category <span class="req">*</span></label>
<select class="fi ${isReadonly?'readonly-field':''}" id="sel-cat-type" ${isReadonly?'disabled':''}>
${Object.entries(ITEM_TYPE_CONFIG).map(([key, cfg]) =>
`<option value="${key}" ${_selectedTypeKey===key?'selected':''}>${cfg.label}</option>`
).join('')}
</select>
</div>
<div class="fg">
<label class="fl">Item Type (SAP)</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="sel-type" ${isReadonly?'disabled':''}>
${ITEM_TYPES.map(t=>`<option value="${t.v}" ${_form.ItemType===t.v?'selected':''}>${t.l}</option>`).join('')}
</select>
</div>
</div>
<div class="fg">
<label class="fl">Item Code <span class="req">*</span></label>
<div class="code-row">
<span class="code-prefix">${prefixDisplay}</span>
<div class="code-input-wrap">
<input class="fi fi-mono ${_mode==='create' && isAuto?'readonly-field':''}" id="inp-code"
placeholder="${_mode==='create'?(isAuto ? 'Auto-generated...' : 'Type item code...'):''}}"
value="${esc(_form.ItemCode)}" autocomplete="off" maxlength="50"
${_mode==='create' && isAuto ? 'readonly style="background:var(--ink2);color:var(--amber);font-weight:600"' : ''}
${_mode!=='create'?'readonly style="background:var(--ink2);color:var(--amber);font-weight:600"':''}/>
</div>
${_mode==='create' ? `
<button class="btn-refresh" id="btn-refresh-code" title="Generate next number" ${isAuto ? '' : 'disabled'}>
↻ Refresh
</button>
<button class="btn-manual ${_isManualMode ? 'on' : ''}" id="btn-toggle-manual" title="Toggle manual/auto">
${_isManualMode ? 'Auto' : '✎ Manual'}
</button>
` : ''}
</div>
<div class="code-meta">
${_mode==='create' && isAuto && _lastSequence !== null ? `
<span class="code-sequence">Last: ${_lastSequence} → Next: ${_form.ItemCode || '—'}</span>
` : ''}
${_mode!=='create' ? `
<span class="code-status">Item Code cannot be changed</span>
` : ''}
${_sequenceError ? `
<span class="code-status" style="color:var(--red)">⚠ ${_sequenceError}</span>
` : ''}
</div>
</div>
<div class="fg">
<label class="fl">Description <span class="req">*</span></label>
<input class="fi ${isReadonly?'readonly-field':''}" id="inp-name" placeholder="Item description..." value="${esc(_form.ItemName)}" ${isReadonly?'readonly':''}/>
</div>
<div class="fi-row">
<div class="fg">
<label class="fl">Item Group</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="sel-group" ${isReadonly?'disabled':''}>
<option value="">- Select Group -</option>
${(_itemGroups.length?_itemGroups:ITEM_GROUPS_DEMO.map(name=>({code:'',name}))).map(g=>`<option value="${esc(g.code)}" ${String(_form.ItemsGroupCode)===String(g.code)?'selected':''}>${esc(g.name)}</option>`).join('')}
</select>
</div>
<div class="fg">
<label class="fl">Material Type</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="sel-mat-type" ${isReadonly?'disabled':''}>
<option value="">— Select —</option>
${MATERIAL_TYPES.map(m=>`<option value="${m.v}" ${_form.MaterialType===m.v?'selected':''}>${m.l}</option>`).join('')}
</select>
</div>
</div>
<hr class="divider"/>
<div class="sec-label">Item Flags</div>
<div class="chk-row">
<label class="chk-item"><input type="checkbox" id="chk-stock" ${_form.InventoryItem==='tYES'?'checked':''} ${isReadonly?'disabled':''}/> Stock Item</label>
<label class="chk-item"><input type="checkbox" id="chk-sales" ${_form.SalesItem==='tYES'?'checked':''} ${isReadonly?'disabled':''}/> Sales Item</label>
<label class="chk-item"><input type="checkbox" id="chk-purchase" ${_form.PurchaseItem==='tYES'?'checked':''} ${isReadonly?'disabled':''}/> Purchasing Item</label>
</div>
</div>`;
app.appendChild(hdr);
const tabs=[
{id:'general',l:'General'},
{id:'purchasing',l:'Purchasing'},
{id:'sales',l:'Sales'},
{id:'stock',l:'Stock Data'},
{id:'remarks',l:'Remarks'}
];
const tabBar = document.createElement('div');
tabBar.className = 'tabs';
tabs.forEach(t=>{
const b = document.createElement('button');
b.className = `tab${_activeTab===t.id?' on':''}`;
b.textContent = t.l; b.dataset.tab = t.id;
b.onclick = ()=>{ _activeTab=t.id; syncFormState(); renderTabContent(); renderTabButtons(); };
tabBar.appendChild(b);
});
app.appendChild(tabBar);
const tabContent = document.createElement('div');
tabContent.id = 'tab-content';
app.appendChild(tabContent);
// Action bar
const actions = document.createElement('div');
actions.className = 'card';
actions.id = 'action-card';
if(_mode==='create'){
actions.innerHTML = `<div class="card-bd"><div class="btn-bar">
<button class="btn b-primary" id="btn-save"><span id="sp-save" style="display:none" class="sp"></span> Add Item</button>
<button class="btn b-cancel" id="btn-reset">Reset</button>
<button class="btn b-view" id="btn-view-list">View Items</button>
</div></div>`;
} else if(_mode==='edit'){
actions.innerHTML = `<div class="card-bd"><div class="btn-bar">
<button class="btn b-primary" id="btn-update"><span id="sp-update" style="display:none" class="sp"></span> Update Item</button>
<button class="btn b-success" id="btn-save-as-new">Save as New</button>
<button class="btn b-danger" id="btn-cancel-item">Cancel Item</button>
<button class="btn b-cancel" id="btn-view-list">Back to List</button>
</div></div>`;
} else {
// view mode
actions.innerHTML = `<div class="card-bd"><div class="btn-bar">
<button class="btn b-primary" id="btn-edit">✎ Edit Item</button>
<button class="btn b-success" id="btn-save-as-new">Save as New</button>
<button class="btn b-cancel" id="btn-view-list">Back to List</button>
</div></div>`;
}
app.appendChild(actions);
renderTabContent();
wireHeaderEvents();
}
function renderTabButtons(){
document.querySelectorAll('.tab').forEach(b=>{
b.classList.toggle('on', b.dataset.tab===_activeTab);
});
}
function renderTabContent(){
const tc = document.getElementById('tab-content');
if(!tc) return;
tc.innerHTML = '';
switch(_activeTab){
case 'general': renderGeneral(tc); break;
case 'purchasing': renderPurchasing(tc); break;
case 'sales': renderSales(tc); break;
case 'stock': renderStock(tc); break;
case 'remarks': renderRemarks(tc); break;
}
if(window.lucide&&lucide.createIcons)lucide.createIcons();
}
function renderGeneral(tc){
const isReadonly = _mode==='view';
const c = document.createElement('div'); c.className = 'card';
c.innerHTML = `<div class="card-hd"><span class="ico"><i data-lucide="info" style="width:16px;height:16px"></i></span> General Information</div>
<div class="card-bd">
<div class="sec-label">Serial & Batch</div>
<div class="fi-row">
<div class="fg"><label class="fl">Manage Serial Numbers</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="g-serial" ${isReadonly?'disabled':''}>
<option value="tNO" ${_form.ManageSerialNumbers==='tNO'?'selected':''}>No</option>
<option value="tYES" ${_form.ManageSerialNumbers==='tYES'?'selected':''}>Yes</option>
</select>
</div>
<div class="fg"><label class="fl">Manage Batch Numbers</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="g-batch" ${isReadonly?'disabled':''}>
<option value="tNO" ${_form.ManageBatchNumbers==='tNO'?'selected':''}>No</option>
<option value="tYES" ${_form.ManageBatchNumbers==='tYES'?'selected':''}>Yes</option>
</select>
</div>
</div>
<hr class="divider"/>
<div class="sec-label">Indian Taxation (GST)</div>
<div class="fi-row-3">
<div class="fg"><label class="fl">Item Category</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="g-icat" ${isReadonly?'disabled':''}>
<option value="Material" ${_form.ItemCategory==='Material'?'selected':''}>Material</option>
<option value="Service" ${_form.ItemCategory==='Service'?'selected':''}>Service</option>
</select>
</div>
<div class="fg"><label class="fl">Material Type</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="g-mattype" ${isReadonly?'disabled':''}>
<option value="">— Select —</option>
${MATERIAL_TYPES.map(m=>`<option value="${m.v}" ${_form.MaterialType===m.v?'selected':''}>${m.l}</option>`).join('')}
</select>
</div>
<div class="fg"><label class="fl">Tax Category</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="g-taxcat" ${isReadonly?'disabled':''}>
<option value="">— Select —</option>
${TAX_CATEGORIES.map(t=>`<option value="${t.v}" ${(_form.GSTTaxCategory||_form.TaxCategory)===t.v?'selected':''}>${t.l}</option>`).join('')}
</select>
</div>
</div>
<div class="fi-row">
<div class="fg"><label class="fl">HSN Code</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="g-hsn" placeholder="e.g. 8536.50.90" value="${esc(_form.HsnCode||'')}" ${isReadonly?'readonly':''}/>
</div>
<div class="fg"><label class="fl">GST</label>
<div style="display:flex;align-items:center;height:36px;gap:12px">
<label class="chk-item"><input type="checkbox" id="g-gst-excisable" ${_form.Excisable==='tYES'?'checked':''} ${isReadonly?'disabled':''}/> Excisable</label>
<label class="chk-item"><input type="checkbox" id="g-gst-gst" ${_form.GSTRelevant!=='tNO'?'checked':''} ${isReadonly?'disabled':''}/> GST</label>
</div>
</div>
</div>
<div class="fi-row">
<div class="fg"><label class="fl">Assessable Value</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="g-assval" type="number" placeholder="0.00" value="${_form.AssessableValue||''}" ${isReadonly?'readonly':''}/>
</div>
</div>
<hr class="divider"/>
<div class="sec-label">Status</div>
<div class="chk-row">
<label class="chk-item"><input type="radio" name="status" value="Active" ${(_form.Frozen==='tNO'||!_form.Frozen)?'checked':''} ${isReadonly?'disabled':''}/> Active</label>
<label class="chk-item"><input type="radio" name="status" value="Inactive" ${_form.Frozen==='tYES'?'checked':''} ${isReadonly?'disabled':''}/> Inactive</label>
</div>
</div>`;
tc.appendChild(c);
if(!isReadonly) bindGeneralEvents();
}
function bindGeneralEvents(){
const w=(id,f,type='text')=>{const el=q(`#${id}`);if(!el)return;el[type==='chk'?'onchange':'oninput']=function(){_form[f]=type==='chk'?(this.checked?'tYES':'tNO'):(type==='num'?parseFloat(this.value)||0:this.value);};};
w('g-serial','ManageSerialNumbers'); w('g-batch','ManageBatchNumbers');
w('g-icat','ItemCategory'); w('g-mattype','MaterialType'); w('g-taxcat','GSTTaxCategory');
w('g-hsn','HsnCode'); w('g-assval','AssessableValue','num');
w('g-gst-excisable','Excisable','chk'); w('g-gst-gst','GSTRelevant','chk');
const ic=q('#g-icat'); if(ic) ic.onchange=function(){_form.ItemCategory=this.value;_form.ItemClass=this.value==='Service'?'itcService':'itcMaterial';};
document.querySelectorAll('[name=status]').forEach(r=>r.onchange=function(){_form.Frozen=this.value==='Inactive'?'tYES':'tNO';});
}
function renderPurchasing(tc){
const isReadonly = _mode==='view';
const c = document.createElement('div'); c.className = 'card';
c.innerHTML = `<div class="card-hd"><span class="ico"><i data-lucide="shopping-cart" style="width:16px;height:16px"></i></span> Purchasing Data</div>
<div class="card-bd">
<div class="fi-row">
<div class="fg"><label class="fl">Preferred Vendor</label>
<input class="fi ${isReadonly?'readonly-field':''}" id="p-vendor" placeholder="Vendor code..." value="${esc(_form.DefaultVendor||'')}" ${isReadonly?'readonly':''}/>
</div>
<div class="fg"><label class="fl">Purchasing UoM</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="p-puom" ${isReadonly?'disabled':''}>
<option value="">- Same as inventory -</option>
<option value="NOS" ${_form.PurchaseUnit==='NOS'?'selected':''}>NOS</option>
<option value="KG" ${_form.PurchaseUnit==='KG'?'selected':''}>KG</option>
<option value="BOX" ${_form.PurchaseUnit==='BOX'?'selected':''}>BOX</option>
<option value="LITRE" ${_form.PurchaseUnit==='LITRE'?'selected':''}>LITRE</option>
<option value="METER" ${_form.PurchaseUnit==='METER'?'selected':''}>METER</option>
</select>
</div>
</div>
<div class="fi-row">
<div class="fg"><label class="fl">Purchase QTY Per Package</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="p-qpp" type="number" min="0" step="0.001" placeholder="0" value="${_form.PurchaseQtyPerPackage||''}" ${isReadonly?'readonly':''}/>
</div>
<div class="fg"><label class="fl">Items Per Purchase Unit</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="p-ipu" type="number" min="0" step="0.001" placeholder="1" value="${_form.PurchaseItemsPerUnit||''}" ${isReadonly?'readonly':''}/>
</div>
</div>
<div class="fi-row">
<div class="fg"><label class="fl">Lead Time (Days)</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="p-lead" type="number" min="0" placeholder="0" value="${_form.LeadTime||''}" ${isReadonly?'readonly':''}/>
</div>
<div class="fg"><label class="fl">Min. Order Qty</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="p-moq" type="number" min="0" step="0.001" placeholder="0" value="${_form.MinimumOrderQuantity||''}" ${isReadonly?'readonly':''}/>
</div>
</div>
</div>`;
tc.appendChild(c);
if(!isReadonly){
const w=(id,f,type='text')=>{const el=q(`#${id}`);if(!el)return;el[type==='select'?'onchange':'oninput']=function(){_form[f]=type==='num'?parseFloat(this.value)||0:this.value;};};
w('p-vendor','DefaultVendor'); w('p-puom','PurchaseUnit','select');
w('p-qpp','PurchaseQtyPerPackage','num'); w('p-ipu','PurchaseItemsPerUnit','num');
w('p-lead','LeadTime','num'); w('p-moq','MinimumOrderQuantity','num');
}
}
function renderSales(tc){
const isReadonly = _mode==='view';
const c = document.createElement('div'); c.className = 'card';
c.innerHTML = `<div class="card-hd"><span class="ico"><i data-lucide="trending-up" style="width:16px;height:16px"></i></span> Sales Data</div>
<div class="card-bd">
<div class="fi-row">
<div class="fg"><label class="fl">Sales UoM</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="s-suom" ${isReadonly?'disabled':''}>
<option value="">- Same as inventory -</option>
<option value="NOS" ${_form.SalesUnit==='NOS'?'selected':''}>NOS</option>
<option value="KG" ${_form.SalesUnit==='KG'?'selected':''}>KG</option>
<option value="BOX" ${_form.SalesUnit==='BOX'?'selected':''}>BOX</option>
<option value="LITRE" ${_form.SalesUnit==='LITRE'?'selected':''}>LITRE</option>
</select>
</div>
<div class="fg"><label class="fl">Sales QTY Per Package</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="s-qpp" type="number" min="0" step="0.001" placeholder="0" value="${_form.SalesQtyPerPackage||''}" ${isReadonly?'readonly':''}/>
</div>
</div>
<div class="fi-row">
<div class="fg"><label class="fl">Min. Order Qty (Sales)</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="s-moq" type="number" min="0" step="0.001" placeholder="0" value="${_form.SalesMinimumOrderQuantity||''}" ${isReadonly?'readonly':''}/>
</div>
<div class="fg"><label class="fl">Warranty (Months)</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="s-warranty" type="number" min="0" placeholder="0" value="${_form.WarrantyTemplate||''}" ${isReadonly?'readonly':''}/>
</div>
</div>
</div>`;
tc.appendChild(c);
if(!isReadonly){
setTimeout(()=>{
const w=(id,f,type='text')=>{const el=q(`#${id}`);if(!el)return;el[type==='select'?'onchange':'oninput']=function(){_form[f]=type==='num'?parseFloat(this.value)||0:this.value;};};
w('s-suom','SalesUnit','select'); w('s-qpp','SalesQtyPerPackage','num');
w('s-moq','SalesMinimumOrderQuantity','num'); w('s-warranty','WarrantyTemplate','num');
},0);
}
}
function renderStock(tc){
const warehouses = _wh.length ? _wh : WH_DEMO;
function buildRows(){
if(!warehouses.length){
return `<tr><td colspan="9" style="text-align:center;padding:16px;color:var(--text3)">No warehouses found</td></tr>`;
}
return warehouses.map((wh,i)=>{
const code = wh.code || '';
const name = wh.name || '';
const branch = wh.branch || '';
const locked = wh.locked ? '✓' : '';
const onHand = wh.onHand||0;
const committed = wh.committed||0;
const onOrder = wh.onOrder||0;
const avail = onHand - committed + onOrder;
const isDefault = _form.DefaultWarehouse === code;
const isSelected = _selectedWhRow === code;
return `<tr class="wh-row${isSelected?' selected':''}${isDefault?' is-default':''}" data-code="${esc(code)}">
<td class="bold">${i+1}</td>
<td><span style="display:inline-flex;align-items:center;gap:4px">
<span style="font-size:9px;color:var(--blue)">→</span> ${esc(code)}
${isDefault?`<span class="star-badge" title="Default Warehouse"><i data-lucide="star" style="width:10px;height:10px"></i></span>`:''}
</span>
</td>
<td class="bold">${esc(name)}</td>
<td>${esc(branch)}</td>
<td style="text-align:center">${locked?`<span style="color:var(--red)">${locked}</span>`:''}</td>
<td style="text-align:right;color:${onHand>0?'var(--green)':'var(--text2)'}">${onHand||''}</td>
<td style="text-align:right">${committed||''}</td>
<td style="text-align:right">${onOrder||''}</td>
<td style="text-align:right;color:${avail>0?'var(--green)':'var(--text2)'}">${avail||''}</td>
</tr>`;
}).join('');
}
const totalStock = warehouses.reduce((s,w)=>s+(w.onHand||0),0);
const totalAvail = warehouses.reduce((s,w)=>s+(w.onHand||0)-(w.committed||0)+(w.onOrder||0),0);
const isReadonly = _mode==='view';
const c = document.createElement('div'); c.className = 'card';
c.innerHTML = `<div class="card-hd"><span class="ico"><i data-lucide="warehouse" style="width:16px;height:16px"></i></span> Stock Data</div>
<div class="card-bd">
<div class="fi-row">
<div class="fg"><label class="fl">Set G/L Accounts By</label>
<select class="fi ${isReadonly?'readonly-field':''}" id="stk-gl" ${isReadonly?'disabled':''}>
<option value="glm_ItemClass" ${_form.GLMethod==='glm_ItemClass'?'selected':''}>Item Group</option>
<option value="glm_WH" ${_form.GLMethod==='glm_WH'?'selected':''}>Warehouse</option>
<option value="glm_ItemLevel" ${_form.GLMethod==='glm_ItemLevel'?'selected':''}>Item Level</option>
</select>
</div>
<div class="fg"><label class="fl">UoM Name</label>
<input class="fi fi-mono ${isReadonly?'readonly-field':''}" id="stk-uomname" placeholder="e.g. Nos" value="${esc(_form.InventoryUOM||'')}" ${isReadonly?'readonly':''}/>
</div>
</div>
${_form.DefaultWarehouse
? `<div class="alert al-green" style="margin-bottom:8px;display:flex;align-items:center;gap:8px">
<span></span>
Default Warehouse: <strong style="font-family:var(--mono)">${esc(_form.DefaultWarehouse)}</strong>
&nbsp;—&nbsp;${esc((warehouses.find(w=>w.code===_form.DefaultWarehouse)||{}).name||'')}
</div>`
: ''
}
<div class="sec-label">Warehouse Stock Levels
<span style="font-size:9px;font-weight:400;color:var(--text3);margin-left:6px;text-transform:none;letter-spacing:0">Click a row to select, then set as default</span>
</div>
<div style="overflow-x:auto">
<table class="stk-table" id="stk-tbl">
<thead>
<tr>
<th>#</th><th>Whse Code</th><th>Whse Name</th><th>Branch</th>
<th>Locked</th><th style="text-align:right">In Stock</th>
<th style="text-align:right">Committed</th><th style="text-align:right">Ordered</th>
<th style="text-align:right">Available</th>
</tr>
</thead>
<tbody id="stk-tbody">${buildRows()}</tbody>
${totalStock>0?`<tfoot>
<tr>
<td colspan="5" style="border-top:1px solid var(--border);padding-top:6px"></td>
<td style="text-align:right;font-family:var(--mono);font-weight:700;color:var(--text);border-top:1px solid var(--border);padding-top:6px">${totalStock}</td>
<td colspan="2" style="border-top:1px solid var(--border)"></td>
<td style="text-align:right;font-family:var(--mono);font-weight:700;color:var(--green);border-top:1px solid var(--border);padding-top:6px">${totalAvail}</td>
</tr>
</tfoot>`:''}
</table>
</div>
${!isReadonly ? `
<div class="whse-btn-row">
<span class="default-label" id="whse-sel-label">
${_selectedWhRow ? `Selected: <strong style="color:var(--text);font-family:var(--mono)">${esc(_selectedWhRow)}</strong>` : 'No warehouse selected'}
</span>
<button class="b-amber" id="btn-set-default-whse" ${_selectedWhRow?'':'disabled'}>
Set Default Whse
</button>
</div>` : ''}
<div class="fi-row" style="margin-top:12px">
<div class="fg"><label class="fl">Min Inventory</label>
<input class="fi" id="stk-min-inv" type="number" min="0" value="${_form.MinimumInventory||0}" ${isReadonly?'readonly':''}/>
</div>
<div class="fg"><label class="fl">Max Inventory</label>
<input class="fi" id="stk-max-inv" type="number" min="0" value="${_form.MaximumQuantityInWarehouse||0}" ${isReadonly?'readonly':''}/>
</div>
<div class="fg"><label class="fl">Desired Inventory</label>
<input class="fi" id="stk-desired-inv" type="number" min="0" value="${_form.DesiredInventory||0}" ${isReadonly?'readonly':''}/>
</div>
</div>
</div>`;
tc.appendChild(c);
setTimeout(()=>{
if(!isReadonly){
const glEl=q('#stk-gl'); if(glEl) glEl.onchange=function(){_form.GLMethod=this.value;};
const uomEl=q('#stk-uomname'); if(uomEl) uomEl.oninput=function(){_form.InventoryUOM=this.value;};
const minEl=q('#stk-min-inv'); if(minEl) minEl.oninput=function(){_form.MinimumInventory=+this.value;};
const maxEl=q('#stk-max-inv'); if(maxEl) maxEl.oninput=function(){_form.MaximumQuantityInWarehouse=+this.value;};
const desEl=q('#stk-desired-inv'); if(desEl) desEl.oninput=function(){_form.DesiredInventory=+this.value;};
}
document.querySelectorAll('#stk-tbody .wh-row').forEach(row=>{
row.addEventListener('click',function(){
if(isReadonly) return;
const code = this.dataset.code;
_selectedWhRow = (_selectedWhRow===code) ? null : code;
document.querySelectorAll('#stk-tbody .wh-row').forEach(r=>{
r.classList.toggle('selected', r.dataset.code===_selectedWhRow);
});
const selLabel = q('#whse-sel-label');
const btn = q('#btn-set-default-whse');
if(selLabel) selLabel.innerHTML = _selectedWhRow
? `Selected: <strong style="color:var(--text);font-family:var(--mono)">${esc(_selectedWhRow)}</strong>`
: 'No warehouse selected';
if(btn) btn.disabled = !_selectedWhRow;
});
});
const defBtn = q('#btn-set-default-whse');
if(defBtn) defBtn.onclick = function(){
if(!_selectedWhRow){toast('Select a warehouse row first','err');return;}
_form.DefaultWarehouse = _selectedWhRow;
toast(`Default warehouse set to: ${_selectedWhRow}`,'ok');
renderTabContent();
};
},0);
}
function renderRemarks(tc){
const isReadonly = _mode==='view';
const c = document.createElement('div'); c.className = 'card';
c.innerHTML = `<div class="card-hd"><span class="ico"><i data-lucide="message-square" style="width:16px;height:16px"></i></span> Remarks</div>
<div class="card-bd">
<div class="fg"><label class="fl">Remarks / Notes</label>
<textarea class="fi ${isReadonly?'readonly-field':''}" id="rem-text" rows="6" placeholder="Additional notes about this item..." ${isReadonly?'readonly':''}>${esc(_form.UserText||'')}</textarea>
</div>
<div class="fg"><label class="fl">Attachments</label>
<div style="border:1.5px dashed var(--border2);border-radius:8px;padding:20px;text-align:center;color:var(--text3);font-size:12px">
<div style="margin-bottom:6px"><i data-lucide="upload-cloud" style="width:24px;height:24px;color:var(--text4)"></i></div>
<div>Drag & drop files or click to attach</div>
<div style="font-size:10px;margin-top:4px;color:var(--text3)">PDF, Images, Excel supported</div>
</div>
</div>
</div>`;
tc.appendChild(c);
if(!isReadonly){
setTimeout(()=>{const el=q('#rem-text');if(el)el.oninput=function(){_form.UserText=this.value;};},0);
}
}
// ════════════════════════════════════════════════════════════════
// WIRE HEADER EVENTS — ALL MODES
// ════════════════════════════════════════════════════════════════
function wireHeaderEvents(){
setTimeout(()=>{
const bind=(id,event,fn)=>{const el=q(id);if(el)el[event]=fn;};
// Category type selector (create mode only)
bind('#sel-cat-type','onchange',async function(){
_selectedTypeKey = this.value;
_sequenceError = null;
const config = ITEM_TYPE_CONFIG[_selectedTypeKey];
_isManualMode = config.defaultManual || !config.hasSeries;
await refreshItemCode();
});
bind('#sel-co','onchange',function(){_co=this.value;});
bind('#inp-code','oninput',function(){_form.ItemCode=this.value.toUpperCase();});
bind('#inp-name','oninput',function(){_form.ItemName=this.value;});
bind('#sel-type','onchange',function(){_form.ItemType=this.value;});
bind('#sel-group','onchange',function(){_form.ItemsGroupCode=this.value;});
bind('#sel-mat-type','onchange',function(){_form.MaterialType=this.value;});
bind('#chk-stock','onchange',function(){_form.InventoryItem=this.checked?'tYES':'tNO';});
bind('#chk-sales','onchange',function(){_form.SalesItem=this.checked?'tYES':'tNO';});
bind('#chk-purchase','onchange',function(){_form.PurchaseItem=this.checked?'tYES':'tNO';});
// ── CREATE MODE ──────────────────────────────────────────────
bind('#btn-save','onclick', doSave);
bind('#btn-reset','onclick',()=>{
if(!confirm('Reset all fields?')) return;
_form=defaultForm(); _selectedWhRow=null; _selectedTypeKey='finished';
_lastSequence=null; _isManualMode=false; _sequenceError=null;
render();
});
bind('#btn-refresh-code','onclick',()=>refreshItemCode(false));
bind('#btn-toggle-manual','onclick', toggleManualMode);
// ── EDIT MODE ────────────────────────────────────────────────
bind('#btn-update','onclick', doUpdate);
bind('#btn-cancel-item','onclick', doCancel);
// ── VIEW MODE ────────────────────────────────────────────────
// FIX: wire the Edit Item button in view mode
bind('#btn-edit','onclick',()=>{
setMode('edit', _currentItemCode);
});
// ── SHARED (edit + view + create) ────────────────────────────
// FIX: wire the Back to List / View Items button in all modes
bind('#btn-view-list','onclick', goToList);
// FIX: wire Save as New button (edit + view modes)
bind('#btn-save-as-new','onclick',()=>{
const oldCode = _form.ItemCode;
_form.ItemCode = '';
_currentItemCode = null;
_mode = 'create';
_lastSequence = null;
_isManualMode = true;
_sequenceError = null;
const badge = q('#mode-badge');
if(badge){ badge.className='mode-badge create'; badge.textContent='Create Mode'; }
render();
toast(`Cloning from ${oldCode} — enter a new item code`, 'warn');
});
// Auto-init sequence for series categories on first create render
const config = ITEM_TYPE_CONFIG[_selectedTypeKey];
if(_mode==='create' && config.hasSeries && !_isManualMode && !_form.ItemCode){
refreshItemCode();
}
}, 0);
}
function syncFormState(){
const fields=[
['inp-code','ItemCode',true],['inp-name','ItemName'],
['g-serial','ManageSerialNumbers'],['g-batch','ManageBatchNumbers'],['g-icat','ItemCategory'],['g-mattype','MaterialType'],
['g-taxcat','GSTTaxCategory'],['g-hsn','HsnCode'],['p-vendor','DefaultVendor'],['p-puom','PurchaseUnit'],
['s-suom','SalesUnit'],['stk-gl','GLMethod'],['stk-uomname','InventoryUOM'],['rem-text','UserText']
];
fields.forEach(([id,f,upper])=>{const el=q(`#${id}`);if(el)_form[f]=upper?el.value.toUpperCase():el.value;});
const nums=[
['g-assval','AssessableValue'],['p-qpp','PurchaseQtyPerPackage'],
['p-ipu','PurchaseItemsPerUnit'],['p-lead','LeadTime'],['p-moq','MinimumOrderQuantity'],
['s-qpp','SalesQtyPerPackage'],['s-moq','SalesMinimumOrderQuantity'],['s-warranty','WarrantyTemplate']
];
nums.forEach(([id,f])=>{const el=q(`#${id}`);if(el&&el.value!=='')_form[f]=parseFloat(el.value)||0;});
const bools=[
['chk-stock','InventoryItem'],['chk-sales','SalesItem'],['chk-purchase','PurchaseItem'],
['g-gst-excisable','Excisable'],['g-gst-gst','GSTRelevant']
];
bools.forEach(([id,f])=>{const el=q(`#${id}`);if(el)_form[f]=el.checked?'tYES':'tNO';});
_form.ItemClass=_form.ItemCategory==='Service'?'itcService':'itcMaterial';
}
function buildItemPayload(includeCode=true){
syncFormState();
const payload={
ItemName:_form.ItemName.trim(),
ForeignName:_form.ForeignName||undefined,
ItemsGroupCode:_form.ItemsGroupCode||undefined,
CustomsGroupCode:-1,
ItemType:_form.ItemType||'itItems',
ItemClass:_form.ItemClass||'itcMaterial',
InventoryItem:_form.InventoryItem,
SalesItem:_form.SalesItem,
PurchaseItem:_form.PurchaseItem,
VatLiable:'tYES',
Valid:'tYES',
Frozen:_form.Frozen||'tNO',
ManageSerialNumbers:_form.ManageSerialNumbers,
ManageBatchNumbers:_form.ManageBatchNumbers,
SRIAndBatchManageMethod:_form.SRIAndBatchManageMethod||'bomm_OnEveryTransaction',
ForceSelectionOfSerialNumber:'tYES',
ManageSerialNumbersOnReleaseOnly:'tNO',
WTLiable:_form.WTLiable||'tNO',
NoDiscounts:_form.NoDiscounts||'tNO',
TreeType:_form.TreeType||'iNotATree',
AssetItem:'tNO',
GLMethod:_form.GLMethod||'glm_ItemClass',
CostAccountingMethod:_form.ValuationMethod||'bis_MovingAverage',
TaxType:'tt_Yes',
IssueMethod:_form.IssueMethod||'im_Manual',
PlanningSystem:_form.PlanningSystem||'bop_None',
ProcurementMethod:_form.ProcurementMethod||'bom_Buy',
ComponentWarehouse:_form.ComponentWarehouse||'bomcw_BOM',
MaterialType:_form.MaterialType||'mt_FinishedGoods',
ProductSource:_form.ProductSource||'0',
UoMGroupEntry:_form.UoMGroupEntry||-1,
Excisable:_form.Excisable||'tNO',
GSTRelevnt:_form.GSTRelevant||'tNO',
GSTTaxCategory:_form.GSTTaxCategory||undefined,
User_Text:_form.UserText||undefined,
InventoryUOM:(_form.InventoryUOM||'').toUpperCase()||undefined,
SalesUnit:(_form.SalesUnit||'').toUpperCase()||undefined,
PurchaseUnit:(_form.PurchaseUnit||'').toUpperCase()||undefined,
Mainsupplier:_form.DefaultVendor||undefined,
OrderMultiple:_form.OrderMultiple||0,
LeadTime:_form.LeadTimeDays||_form.LeadTime||undefined,
MinOrderQuantity:_form.MinimumOrderQuantity||0,
ProdStdCost:_form.ProductionStandardCost||0,
InCostRollup:_form.IncludeInStdCostRollup||'tYES',
AssessableValue:_form.AssessableValue||0,
ManageStockByWarehouse:'tNO',
PurchaseItemsPerUnit:_form.PurchaseItemsPerUnit||1,
PurchaseQtyPerPackUnit:_form.PurchaseQtyPerPackage||1,
SalesItemsPerUnit:1,
SalesQtyPerPackUnit:_form.SalesQtyPerPackage||1,
WarrantyTemplate:_form.WarrantyTemplate||undefined,
DefaultWarehouse:_form.DefaultWarehouse||undefined,
HsnCode:_form.HsnCode||undefined,
MinInventory:_form.MinimumInventory||0,
MaxInventory:_form.MaximumQuantityInWarehouse||0,
DesiredInventory:_form.DesiredInventory||0,
company:_co
};
if(includeCode) payload.ItemCode=_form.ItemCode.trim().toUpperCase();
if(_form.HsnCode&&/^\d+$/.test(String(_form.HsnCode))) payload.ChapterID=parseInt(_form.HsnCode);
Object.keys(payload).forEach(k=>{if(payload[k]===''||payload[k]===null||payload[k]===undefined)delete payload[k];});
return payload;
}
async function doSave(){
syncFormState();
if(!_form.ItemCode.trim()){toast('Item Code is required','err');return;}
if(!_form.ItemName.trim()){toast('Description is required','err');return;}
const btn=q('#btn-save'), sp=q('#sp-save');
btn.disabled=true; sp.style.display='inline-block';
const payload=buildItemPayload(true);
const role=_user?.role||'';
const isPrivileged = role==='admin'||role==='sap_adder';
try{
if(isPrivileged){
// admin / sap_adder — push directly to SAP
const r=await api('POST','/sap/items',payload);
const itemCode=_form.ItemCode.trim().toUpperCase();
if(_form.DefaultWarehouse){
try{
await api('PATCH',`/sap/items/${encodeURIComponent(itemCode)}`,{DefaultWarehouse:_form.DefaultWarehouse,company:_co});
}catch(we){
console.warn('[doSave] DefaultWarehouse PATCH failed:',we.message);
toast('Item created but default warehouse failed: '+we.message,'warn');
}
}
showSuccess(r.data||{ItemCode:itemCode,ItemName:_form.ItemName});
} else {
// user / manager / sr_manager — submit for approval
const r=await api('POST','/item-approvals/submit',payload);
if(r.status==='SAP_PUSHED'){
showSuccess({ItemCode:r.itemCode||_form.ItemCode,ItemName:_form.ItemName});
} else {
showPending(_form.ItemCode.trim().toUpperCase(),_form.ItemName,r.id);
}
}
}catch(e){
toast(e.message||'Failed to create item','err');
btn.disabled=false; sp.style.display='none';
}
}
async function doUpdate(){
if(!_form.ItemCode.trim()){toast('Item Code is required','err');return;}
if(!_form.ItemName.trim()){toast('Description is required','err');return;}
const btn=q('#btn-update'), sp=q('#sp-update');
if(btn) btn.disabled=true; if(sp) sp.style.display='inline-block';
const itemCode=_form.ItemCode.trim().toUpperCase();
const payload=buildItemPayload(false);
if(_form.DefaultWarehouse) payload.DefaultWarehouse=_form.DefaultWarehouse;
console.log('[doUpdate] payload:', JSON.stringify(payload, null, 2));
try{
await api('PATCH',`/sap/items/${encodeURIComponent(itemCode)}`,payload);
toast('Item updated: '+itemCode,'ok');
}catch(e){
toast(e.message||'Failed to update item','err');
}finally{
if(btn) btn.disabled=false; if(sp) sp.style.display='none';
}
}
async function doCancel(){
if(!_form.ItemCode.trim()){toast('No item to cancel','err');return;}
if(!confirm(`Cancel item ${_form.ItemCode}?`)) return;
try{
await api('POST',`/sap/items/${encodeURIComponent(_form.ItemCode)}/cancel`,{company:_co});
toast('Item cancelled');
}catch(e){
toast(e.message,'err');
}
}
function showSuccess(data){
const app=document.getElementById('app');
app.innerHTML=`<div class="card">
<div class="card-hd"><span class="ico"><i data-lucide="check-circle" style="width:16px;height:16px"></i></span> Item Created</div>
<div class="card-bd" style="text-align:center;padding:30px 16px">
<div style="margin-bottom:10px"><i data-lucide="check-circle-2" style="width:44px;height:44px;color:var(--green)"></i></div>
<div style="font-family:var(--font);font-weight:800;font-size:22px;color:var(--green);margin-bottom:6px">ITEM ADDED</div>
<div class="item-code-display" style="margin-bottom:4px">${esc(data.ItemCode||_form.ItemCode)}</div>
<div style="font-size:13px;color:var(--text3);margin-bottom:12px">${esc(data.ItemName||_form.ItemName)}</div>
${_form.DefaultWarehouse?`<div style="font-size:11px;color:var(--amber);margin-bottom:10px">Default Warehouse: <strong>${esc(_form.DefaultWarehouse)}</strong></div>`:''}
<div class="alert al-green" style="max-width:420px;margin:0 auto 16px;text-align:left">
Item successfully created in <strong>${_co}</strong> with status <strong>Active</strong>
</div>
<div class="btn-bar" style="justify-content:center">
<button class="btn b-primary" id="btn-new-item">+ New Item</button>
<button class="btn b-view" id="btn-view-items">View Items</button>
</div>
</div>
</div>`;
if(window.lucide&&lucide.createIcons)lucide.createIcons();
setTimeout(()=>{
q('#btn-new-item').onclick=()=>{
_form=defaultForm(); _selectedWhRow=null; _selectedTypeKey='finished';
_lastSequence=null; _isManualMode=false; _sequenceError=null; _activeTab='general';
render();
};
// FIX: properly navigate to list instead of just showing a toast
q('#btn-view-items').onclick=()=>goToList();
},0);
}
function showPending(itemCode, itemName, id){
const app=document.getElementById('app');
app.innerHTML=`<div class="card">
<div class="card-hd"><span class="ico"><i data-lucide="clock" style="width:16px;height:16px"></i></span> Approval Pending</div>
<div class="card-bd" style="text-align:center;padding:30px 16px">
<div style="margin-bottom:10px"><i data-lucide="clock" style="width:44px;height:44px;color:var(--amber)"></i></div>
<div style="font-family:var(--font);font-weight:800;font-size:22px;color:var(--amber);margin-bottom:6px">SUBMITTED FOR APPROVAL</div>
<div class="item-code-display" style="margin-bottom:4px">${esc(itemCode)}</div>
<div style="font-size:13px;color:var(--text3);margin-bottom:12px">${esc(itemName)}</div>
<div class="alert" style="background:rgba(245,166,35,.08);border:1px solid rgba(245,166,35,.25);color:var(--amber);max-width:420px;margin:0 auto 16px;text-align:left;padding:10px 14px;border-radius:8px;font-size:12px">
Your item request has been submitted and is awaiting admin approval before being added to SAP B1.
</div>
<div class="btn-bar" style="justify-content:center">
<button class="btn b-primary" id="btn-new-item">+ New Item</button>
</div>
</div>
</div>`;
if(window.lucide&&lucide.createIcons)lucide.createIcons();
setTimeout(()=>{
q('#btn-new-item').onclick=()=>{
_form=defaultForm(); _selectedWhRow=null; _selectedTypeKey='finished';
_lastSequence=null; _isManualMode=false; _sequenceError=null; _activeTab='general';
render();
};
},0);
}
async function init(){
render();
try{const list=await window.fetchCompanyList();if(list.length)_companies=list;}catch(_e){}
await loadLookups();
render();
}
init();
</script>
<script src="/sidebar.js?v=25"></script>
</body>
</html>