403Webshell
Server IP : 121.121.20.254  /  Your IP : 216.73.217.51
Web Server : Microsoft-IIS/10.0
System : Windows NT WEB-SERVER 10.0 build 20348 (Windows Server 2022) AMD64
User : IUSR ( 0)
PHP Version : 8.3.28
Disable Function : NONE
MySQL : ON  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  C:/inetpub/wwwroot/AIWEBSTATION/wp-content/novamira-sandbox/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : C:/inetpub/wwwroot/AIWEBSTATION/wp-content/novamira-sandbox/wms-workspace-pro.php
<?php
/** WMS Workspace Pro extension for AKWMS v0.2.0. */
if (!defined('ABSPATH')) { return; }

final class AKWMS_Workspace_Pro {
    public static function init(): void {
        add_action('init', [__CLASS__, 'override_shortcode'], 99);
        add_action('rest_api_init', [__CLASS__, 'routes'], 99);
    }

    public static function override_shortcode(): void {
        if (!class_exists('AKWMS_Plugin')) return;
        remove_shortcode('wms_operator');
        add_shortcode('wms_operator', [__CLASS__, 'shortcode']);
    }

    private static function can_view(): bool { return current_user_can('akwms_view'); }
    private static function can_master(): bool { return current_user_can('akwms_manage_master') || current_user_can('manage_options'); }
    private static function can_orders(): bool { return current_user_can('akwms_manage_orders') || current_user_can('manage_woocommerce') || current_user_can('manage_options'); }
    private static function can_waves(): bool { return current_user_can('akwms_manage_waves') || current_user_can('manage_options'); }

    public static function routes(): void {
        $ns = 'wms/v1';
        register_rest_route($ns, '/workspace/master', ['methods'=>'GET','callback'=>[__CLASS__,'master'],'permission_callback'=>[__CLASS__,'can_view_public']]);
        register_rest_route($ns, '/workspace/bootstrap', ['methods'=>'GET','callback'=>[__CLASS__,'bootstrap'],'permission_callback'=>[__CLASS__,'can_view_public']]);        register_rest_route($ns, '/workspace/receipts', ['methods'=>'GET','callback'=>[__CLASS__,'receipts'],'permission_callback'=>[__CLASS__,'can_view_public']]);        register_rest_route($ns, '/workspace/receive-commit', ['methods'=>'POST','callback'=>[__CLASS__,'receive_commit'],'permission_callback'=>[__CLASS__,'can_receive_public']]);
        register_rest_route($ns, '/workspace/warehouses', ['methods'=>'POST','callback'=>[__CLASS__,'create_warehouse'],'permission_callback'=>[__CLASS__,'can_master_public']]);
        register_rest_route($ns, '/workspace/skus', ['methods'=>'POST','callback'=>[__CLASS__,'create_sku'],'permission_callback'=>[__CLASS__,'can_master_public']]);
        register_rest_route($ns, '/workspace/locations', ['methods'=>'POST','callback'=>[__CLASS__,'create_location'],'permission_callback'=>[__CLASS__,'can_master_public']]);
        register_rest_route($ns, '/workspace/lpns', ['methods'=>'POST','callback'=>[__CLASS__,'create_lpn'],'permission_callback'=>[__CLASS__,'can_master_public']]);
        register_rest_route($ns, '/workspace/waves', ['methods'=>'GET','callback'=>[__CLASS__,'waves'],'permission_callback'=>[__CLASS__,'can_view_public']]);
        register_rest_route($ns, '/workspace/waves', ['methods'=>'POST','callback'=>[__CLASS__,'create_wave'],'permission_callback'=>[__CLASS__,'can_waves_public']]);
        register_rest_route($ns, '/workspace/waves/(?P<id>\\d+)/generate', ['methods'=>'POST','callback'=>[__CLASS__,'generate_wave'],'permission_callback'=>[__CLASS__,'can_waves_public']]);
        register_rest_route($ns, '/workspace/tasks', ['methods'=>'GET','callback'=>[__CLASS__,'workspace_tasks'],'permission_callback'=>[__CLASS__,'can_view_public']]);
        register_rest_route($ns, '/workspace/reset-test', ['methods'=>'POST','callback'=>[__CLASS__,'reset_test'],'permission_callback'=>[__CLASS__,'can_master_public']]);
        register_rest_route($ns, '/workspace/orders/(?P<id>\\d+)/ship', ['methods'=>'POST','callback'=>[__CLASS__,'ship_order'],'permission_callback'=>[__CLASS__,'can_orders_public']]);
        register_rest_route($ns, '/workspace/orders/(?P<id>\\d+)/details', ['methods'=>'GET','callback'=>[__CLASS__,'order_details'],'permission_callback'=>[__CLASS__,'can_view_public']]);
        register_rest_route($ns, '/workspace/orders-list', ['methods'=>'GET','callback'=>[__CLASS__,'orders_list'],'permission_callback'=>[__CLASS__,'can_view_public']]);
        register_rest_route($ns, '/workspace/catalog-search', ['methods'=>'GET','callback'=>[__CLASS__,'catalog_search'],'permission_callback'=>[__CLASS__,'can_view_public']]);
        register_rest_route($ns, '/workspace/inventory-list', ['methods'=>'GET','callback'=>[__CLASS__,'inventory_list'],'permission_callback'=>[__CLASS__,'can_view_public']]);
        register_rest_route($ns, '/workspace/exceptions', ['methods'=>'GET','callback'=>[__CLASS__,'exceptions'],'permission_callback'=>[__CLASS__,'can_view_public']]);
        register_rest_route($ns, '/workspace/orders/(?P<id>\d+)/cancel', ['methods'=>'POST','callback'=>[__CLASS__,'cancel_order'],'permission_callback'=>[__CLASS__,'can_orders_public']]);
        register_rest_route($ns, '/workspace/counts', ['methods'=>'GET','callback'=>[__CLASS__,'counts'],'permission_callback'=>[__CLASS__,'can_count_public']]);
        register_rest_route($ns, '/workspace/counts', ['methods'=>'POST','callback'=>[__CLASS__,'create_count'],'permission_callback'=>[__CLASS__,'can_count_public']]);
        register_rest_route($ns, '/workspace/counts/(?P<id>\d+)/complete', ['methods'=>'POST','callback'=>[__CLASS__,'complete_count'],'permission_callback'=>[__CLASS__,'can_count_public']]);
        register_rest_route($ns, '/workspace/putaway', ['methods'=>'GET','callback'=>[__CLASS__,'putaway_queue'],'permission_callback'=>[__CLASS__,'can_putaway_public']]);
        register_rest_route($ns, '/workspace/putaway/(?P<id>\d+)', ['methods'=>'POST','callback'=>[__CLASS__,'putaway_stock'],'permission_callback'=>[__CLASS__,'can_putaway_public']]);
        register_rest_route($ns, '/workspace/packing', ['methods'=>'GET','callback'=>[__CLASS__,'packing_queue'],'permission_callback'=>[__CLASS__,'can_pack_public']]);
        register_rest_route($ns, '/workspace/orders/(?P<id>\d+)/pack', ['methods'=>'POST','callback'=>[__CLASS__,'pack_order'],'permission_callback'=>[__CLASS__,'can_pack_public']]);
        register_rest_route($ns, '/workspace/returns', ['methods'=>'GET','callback'=>[__CLASS__,'returns_list'],'permission_callback'=>[__CLASS__,'can_returns_public']]);
        register_rest_route($ns, '/workspace/returns', ['methods'=>'POST','callback'=>[__CLASS__,'create_return'],'permission_callback'=>[__CLASS__,'can_returns_public']]);
        register_rest_route($ns, '/workspace/integrations', ['methods'=>'GET','callback'=>[__CLASS__,'integration_status'],'permission_callback'=>[__CLASS__,'can_view_public']]);    }
    public static function can_view_public(): bool { return self::can_view(); }
    public static function can_master_public(): bool { return self::can_master(); }
    public static function can_orders_public(): bool { return self::can_orders(); }
    public static function can_waves_public(): bool { return self::can_waves(); }
    public static function can_receive_public(): bool { return current_user_can('akwms_receive') || current_user_can('manage_options'); }
    public static function can_count_public(): bool { return current_user_can('akwms_count') || current_user_can('manage_options'); }
    public static function can_putaway_public(): bool { return current_user_can('akwms_putaway') || current_user_can('manage_options'); }
    public static function can_pack_public(): bool { return current_user_can('akwms_pack') || current_user_can('manage_options'); }
    public static function can_returns_public(): bool { return current_user_can('akwms_returns') || current_user_can('manage_options'); }
    private static function reason_code(string $value, array $allowed, string $fallback): string {
        $v=strtoupper(sanitize_key($value)); return in_array($v,$allowed,true)?$v:$fallback;
    }

    public static function counts(WP_REST_Request $r) {
        global $wpdb; $t=AKWMS_DB::table('cycle_counts'); $inv=AKWMS_DB::table('inventory');
        $sql="SELECT c.*,i.warehouse_id,i.location_id,i.lpn_id,i.sku_id,i.on_hand current_on_hand,i.reserved,s.sku,s.name sku_name,w.code warehouse_code,l.code location_code,p.code lpn_code,u.display_name counted_by_name FROM $t c JOIN $inv i ON i.id=c.inventory_id JOIN ".AKWMS_DB::table('skus')." s ON s.id=i.sku_id JOIN ".AKWMS_DB::table('warehouses')." w ON w.id=i.warehouse_id JOIN ".AKWMS_DB::table('locations')." l ON l.id=i.location_id LEFT JOIN ".AKWMS_DB::table('lpns')." p ON p.id=i.lpn_id LEFT JOIN {$wpdb->users} u ON u.ID=c.counted_by ORDER BY FIELD(c.status,'OPEN','PENDING_APPROVAL','COMPLETED'),c.id DESC LIMIT 100";
        return $wpdb->get_results($sql,ARRAY_A);
    }

    public static function create_count(WP_REST_Request $r) {
        global $wpdb; $d=$r->get_json_params(); $iid=(int)($d['inventory_id']??0); if(!$iid)return new WP_Error('missing_inventory','Choose an inventory row to count',['status'=>400]);
        $exists=(int)$wpdb->get_var($wpdb->prepare("SELECT id FROM ".AKWMS_DB::table('cycle_counts')." WHERE inventory_id=%d AND status IN ('OPEN','PENDING_APPROVAL') LIMIT 1",$iid));
        if($exists)return new WP_Error('count_exists','A stock count is already open for this inventory',['status'=>409]);
        $reason=self::reason_code((string)($d['reason']??''),['ROUTINE_COUNT','MISSING_STOCK','DAMAGE_CHECK','LOCATION_CHECK','MANUAL_CHECK'],'ROUTINE_COUNT');
        $x=AKWMS_Inventory::create_cycle_count($iid,$reason); return is_wp_error($x)?$x:new WP_REST_Response(['id'=>$x],201);
    }

    public static function complete_count(WP_REST_Request $r) {
        $d=$r->get_json_params(); $qty=(float)($d['counted_qty']??-1); if($qty<0)return new WP_Error('bad_qty','Actual quantity cannot be negative',['status'=>400]);
        $reason=self::reason_code((string)($d['reason']??''),['COUNT_VARIANCE','DAMAGE_FOUND','STOCK_FOUND','WRONG_LOCATION','DATA_CORRECTION'],'COUNT_VARIANCE');
        $x=AKWMS_Inventory::complete_cycle_count((int)$r['id'],$qty,$reason); return is_wp_error($x)?$x:['completed'=>(bool)$x];
    }

    public static function putaway_queue() {
        global $wpdb; $inv=AKWMS_DB::table('inventory');
        $sql="SELECT i.*,GREATEST(i.on_hand-i.reserved,0) available_qty,s.sku,s.name sku_name,w.code warehouse_code,l.code location_code,p.code lpn_code FROM $inv i JOIN ".AKWMS_DB::table('skus')." s ON s.id=i.sku_id JOIN ".AKWMS_DB::table('warehouses')." w ON w.id=i.warehouse_id JOIN ".AKWMS_DB::table('locations')." l ON l.id=i.location_id LEFT JOIN ".AKWMS_DB::table('lpns')." p ON p.id=i.lpn_id WHERE i.on_hand>0 AND l.location_type='RECEIVING_AREA' ORDER BY i.id LIMIT 100";
        return $wpdb->get_results($sql,ARRAY_A);
    }

    public static function putaway_stock(WP_REST_Request $r) {
        global $wpdb; $d=$r->get_json_params(); $iid=(int)$r['id']; $dest=(int)($d['to_location_id']??0); $qty=(float)($d['quantity']??0);
        $loc=$wpdb->get_row($wpdb->prepare("SELECT id,warehouse_id,code,location_type,status FROM ".AKWMS_DB::table('locations')." WHERE id=%d",$dest),ARRAY_A);
        if(!$loc||$loc['status']!=='ACTIVE'||$loc['location_type']!=='STORAGE_BIN')return new WP_Error('bad_destination','Putaway destination must be an active Storage bin',['status'=>400]);
        $row=$wpdb->get_row($wpdb->prepare("SELECT warehouse_id FROM ".AKWMS_DB::table('inventory')." WHERE id=%d",$iid),ARRAY_A); if(!$row)return new WP_Error('not_found','Inventory not found',['status'=>404]);
        if((int)$row['warehouse_id']!==(int)$loc['warehouse_id'])return new WP_Error('wrong_warehouse','Putaway must stay inside the same warehouse',['status'=>409]);
        $x=AKWMS_Inventory::move($iid,$dest,$qty,'PUTAWAY'); return is_wp_error($x)?$x:['putaway'=>(bool)$x];
    }

    public static function packing_queue() {
        global $wpdb; $o=AKWMS_DB::table('orders'); $oi=AKWMS_DB::table('order_items'); $wo=AKWMS_DB::table('wave_orders'); $t=AKWMS_DB::table('tasks');
        $sql="SELECT o.id,o.order_no,o.customer_name,o.source_channel,o.status,o.priority,COALESCE(SUM(oi.ordered_qty),0) ordered_qty,(SELECT COUNT(*) FROM $t tx JOIN $wo wx ON wx.wave_id=tx.wave_id WHERE wx.order_id=o.id) task_count,(SELECT COUNT(*) FROM $t tx JOIN $wo wx ON wx.wave_id=tx.wave_id WHERE wx.order_id=o.id AND tx.status<>'COMPLETED') open_task_count FROM $o o LEFT JOIN $oi oi ON oi.order_id=o.id WHERE o.status IN ('ALLOCATED','PICKED','PACKED') GROUP BY o.id HAVING task_count>0 AND open_task_count=0 ORDER BY o.id DESC LIMIT 100";
        return $wpdb->get_results($sql,ARRAY_A);
    }

    public static function pack_order(WP_REST_Request $r) {
        global $wpdb; $oid=(int)$r['id']; $orders=AKWMS_DB::table('orders'); $items=AKWMS_DB::table('order_items'); $wo=AKWMS_DB::table('wave_orders'); $tasks=AKWMS_DB::table('tasks');
        $o=$wpdb->get_row($wpdb->prepare("SELECT * FROM $orders WHERE id=%d",$oid),ARRAY_A); if(!$o)return new WP_Error('not_found','Order not found',['status'=>404]);
        if($o['status']==='PACKED')return ['packed'=>true,'already'=>true]; if(!in_array($o['status'],['ALLOCATED','PICKED'],true))return new WP_Error('bad_status','Only picked warehouse orders can be packed',['status'=>409]);
        $total=(int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $tasks t JOIN $wo wo ON wo.wave_id=t.wave_id WHERE wo.order_id=%d",$oid));
        $open=(int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $tasks t JOIN $wo wo ON wo.wave_id=t.wave_id WHERE wo.order_id=%d AND t.status<>'COMPLETED'",$oid));
        if(!$total||$open)return new WP_Error('not_ready','Finish all picking tasks before packing this order',['status'=>409]);
        $wpdb->query('START TRANSACTION'); try{$wpdb->query($wpdb->prepare("UPDATE $items SET picked_qty=ordered_qty,packed_qty=ordered_qty,status='PACKED' WHERE order_id=%d",$oid)); $wpdb->update($orders,['status'=>'PACKED','updated_at'=>current_time('mysql')],['id'=>$oid]); $wpdb->query('COMMIT'); AKWMS_Audit::log('order_packed','order',$oid,'Order packed and verified'); return ['packed'=>true];}catch(Throwable $e){$wpdb->query('ROLLBACK');return new WP_Error('pack_failed',$e->getMessage(),['status'=>409]);}
    }

    public static function returns_list() {
        global $wpdb; $t=$wpdb->prefix.'akwms_returns';
        $sql="SELECT r.*,o.order_no,s.sku,s.name sku_name,w.code warehouse_code,l.code location_code,u.display_name user_name FROM $t r JOIN ".AKWMS_DB::table('orders')." o ON o.id=r.order_id JOIN ".AKWMS_DB::table('skus')." s ON s.id=r.sku_id JOIN ".AKWMS_DB::table('warehouses')." w ON w.id=r.warehouse_id JOIN ".AKWMS_DB::table('locations')." l ON l.id=r.location_id LEFT JOIN {$wpdb->users} u ON u.ID=r.created_by ORDER BY r.id DESC LIMIT 100"; return $wpdb->get_results($sql,ARRAY_A);
    }

    public static function create_return(WP_REST_Request $r) {
        global $wpdb; $d=$r->get_json_params(); $oid=(int)($d['order_id']??0); $sid=(int)($d['sku_id']??0); $qty=(float)($d['quantity']??0); if(!$oid||!$sid||$qty<=0)return new WP_Error('missing','Order, SKU and positive quantity are required',['status'=>400]);
        $order=$wpdb->get_row($wpdb->prepare("SELECT * FROM ".AKWMS_DB::table('orders')." WHERE id=%d",$oid),ARRAY_A); if(!$order||$order['status']!=='SHIPPED')return new WP_Error('not_shipped','Returns can only be recorded against shipped orders',['status'=>409]);
        $item=$wpdb->get_row($wpdb->prepare("SELECT * FROM ".AKWMS_DB::table('order_items')." WHERE order_id=%d AND sku_id=%d",$oid,$sid),ARRAY_A); if(!$item)return new WP_Error('sku_not_order','That SKU is not on this order',['status'=>409]);
        $rt=$wpdb->prefix.'akwms_returns'; $already=(float)$wpdb->get_var($wpdb->prepare("SELECT COALESCE(SUM(quantity),0) FROM $rt WHERE order_id=%d AND sku_id=%d AND status='COMPLETED'",$oid,$sid)); if($already+$qty>(float)$item['shipped_qty']+0.0001)return new WP_Error('too_many','Return quantity exceeds the quantity shipped',['status'=>409]);
        $disp=self::reason_code((string)($d['disposition']??''),['QUARANTINE','DAMAGED','RESTOCK'],'QUARANTINE'); $reason=self::reason_code((string)($d['reason']??''),['CUSTOMER_RETURN','WRONG_ITEM','DAMAGED_IN_TRANSIT','UNWANTED','FAILED_DELIVERY','OTHER'],'CUSTOMER_RETURN');
        $wid=(int)($order['warehouse_id']?:$wpdb->get_var($wpdb->prepare("SELECT warehouse_id FROM ".AKWMS_DB::table('inventory_transactions')." WHERE order_id=%d AND transaction_type='SHIP' ORDER BY id DESC LIMIT 1",$oid))); if(!$wid)return new WP_Error('warehouse_missing','Could not determine the return warehouse',['status'=>409]);
        $type=$disp==='RESTOCK'?'STORAGE_BIN':'RETURN_AREA'; $lid=(int)$wpdb->get_var($wpdb->prepare("SELECT id FROM ".AKWMS_DB::table('locations')." WHERE warehouse_id=%d AND location_type=%s AND status='ACTIVE' ORDER BY id LIMIT 1",$wid,$type)); if(!$lid)return new WP_Error('return_location_missing','Create an active '.$type.' location before recording this return',['status'=>409]);
        $status=$disp==='RESTOCK'?'AVAILABLE':($disp==='DAMAGED'?'DAMAGED':'QUARANTINE'); $x=AKWMS_Inventory::receive(['warehouse_id'=>$wid,'location_id'=>$lid,'sku_id'=>$sid,'quantity'=>$qty,'inventory_status'=>$status,'reason'=>'RETURN_'.$disp]); if(is_wp_error($x))return $x;
        $no='RTN-'.gmdate('YmdHis').'-'.wp_rand(100,999); $wpdb->insert($rt,['return_no'=>$no,'order_id'=>$oid,'sku_id'=>$sid,'quantity'=>$qty,'disposition'=>$disp,'warehouse_id'=>$wid,'location_id'=>$lid,'reason'=>$reason,'notes'=>sanitize_textarea_field($d['notes']??''),'status'=>'COMPLETED','created_by'=>get_current_user_id()?:null,'created_at'=>current_time('mysql')]); $rid=(int)$wpdb->insert_id; AKWMS_Audit::log('return_received','return',$rid,'Customer return recorded',['order_id'=>$oid,'sku_id'=>$sid,'qty'=>$qty,'disposition'=>$disp]); return new WP_REST_Response(['id'=>$rid,'return_no'=>$no],201);
    }

    public static function integration_status() {
        global $wpdb; $q=$wpdb->prefix.'akwms_integration_queue'; return ['ready'=>true,'credentials_configured'=>false,'message'=>'Integration framework ready. Seller API credentials are intentionally not stored yet.','queue'=>['pending'=>(int)$wpdb->get_var("SELECT COUNT(*) FROM $q WHERE status='PENDING'"),'failed'=>(int)$wpdb->get_var("SELECT COUNT(*) FROM $q WHERE status='FAILED'")],'channels'=>[['code'=>'WOOCOMMERCE','status'=>'Awaiting API credentials'],['code'=>'SHOPEE','status'=>'Awaiting seller API credentials'],['code'=>'LAZADA','status'=>'Awaiting seller API credentials']]];
    }
    public static function bootstrap() {
        $req=new WP_REST_Request('GET','/');
        $req->set_param('page',1);
        $req->set_param('per_page',50);
        $waves=self::waves();
        return [
            'master'=>self::master(),
            'dashboard'=>AKWMS_REST::dashboard(),
            'inventory'=>self::inventory_list($req),
            'orders'=>self::orders_list($req),
            'waves'=>$waves,
            'tasks'=>self::workspace_tasks(),
            'receipts'=>self::receipts(),
            'exceptions'=>self::exceptions(),
        ];
    }
    public static function master() {
        global $wpdb;
        $skuT=AKWMS_DB::table('skus'); $lpnT=AKWMS_DB::table('lpns');
        $skus=$wpdb->get_results("SELECT id,sku,name,barcode,uom,item_class,allocation_strategy,is_active FROM $skuT WHERE is_active=1 ORDER BY id DESC LIMIT 200", ARRAY_A);
        $testSku=$wpdb->get_row($wpdb->prepare("SELECT id,sku,name,barcode,uom,item_class,allocation_strategy,is_active FROM $skuT WHERE sku=%s",'TEST-SKU-001'),ARRAY_A);
        if($testSku&&!array_filter($skus,fn($x)=>$x['id']===$testSku['id']))$skus[]=$testSku;
        $lpns=$wpdb->get_results("SELECT p.id,p.code,p.lpn_type,p.warehouse_id,p.location_id,p.operational_status,l.code location_code FROM $lpnT p LEFT JOIN ".AKWMS_DB::table('locations')." l ON l.id=p.location_id ORDER BY p.id DESC LIMIT 200", ARRAY_A);
        $testLpn=$wpdb->get_row($wpdb->prepare("SELECT p.id,p.code,p.lpn_type,p.warehouse_id,p.location_id,p.operational_status,l.code location_code FROM $lpnT p LEFT JOIN ".AKWMS_DB::table('locations')." l ON l.id=p.location_id WHERE p.code=%s",'PLT-TEST-001'),ARRAY_A);
        if($testLpn&&!array_filter($lpns,fn($x)=>$x['id']===$testLpn['id']))$lpns[]=$testLpn;
        return [
            'warehouses'=>$wpdb->get_results("SELECT id,code,name,address,is_active FROM ".AKWMS_DB::table('warehouses')." ORDER BY code", ARRAY_A),
            'locations'=>$wpdb->get_results("SELECT l.id,l.warehouse_id,l.code,l.location_type,l.status,l.is_pickable,l.is_allocatable,w.code warehouse_code FROM ".AKWMS_DB::table('locations')." l LEFT JOIN ".AKWMS_DB::table('warehouses')." w ON w.id=l.warehouse_id ORDER BY w.code,l.code", ARRAY_A),
            'skus'=>$skus,
            'lpns'=>$lpns,
            'counts'=>['skus'=>(int)$wpdb->get_var("SELECT COUNT(*) FROM $skuT WHERE is_active=1"),'lpns'=>(int)$wpdb->get_var("SELECT COUNT(*) FROM $lpnT")],
            'catalog_limited'=>true,
            'caps'=>['master'=>self::can_master(),'orders'=>self::can_orders(),'waves'=>self::can_waves(),'receive'=>current_user_can('akwms_receive')||current_user_can('manage_options')],
        ];
    }

    public static function catalog_search(WP_REST_Request $r) {
        global $wpdb; $type=sanitize_key($r->get_param('type')?:'sku'); $q=trim(sanitize_text_field($r->get_param('q')?:'')); $like='%'.$wpdb->esc_like($q).'%';
        if($type==='sku'){
            $t=AKWMS_DB::table('skus');
            if($q==='') return $wpdb->get_results("SELECT id,sku,name,barcode,uom FROM $t WHERE is_active=1 ORDER BY id DESC LIMIT 30",ARRAY_A);
            return $wpdb->get_results($wpdb->prepare("SELECT id,sku,name,barcode,uom FROM $t WHERE is_active=1 AND (sku LIKE %s OR name LIKE %s OR barcode LIKE %s) ORDER BY CASE WHEN sku=%s THEN 0 WHEN sku LIKE %s THEN 1 ELSE 2 END,sku LIMIT 30",$like,$like,$like,$q,$q.'%'),ARRAY_A);
        }
        if($type==='lpn'){
            $t=AKWMS_DB::table('lpns');
            if($q==='') return $wpdb->get_results("SELECT p.id,p.code,p.lpn_type,p.warehouse_id,p.location_id,p.operational_status,l.code location_code FROM $t p LEFT JOIN ".AKWMS_DB::table('locations')." l ON l.id=p.location_id ORDER BY p.id DESC LIMIT 30",ARRAY_A);
            return $wpdb->get_results($wpdb->prepare("SELECT p.id,p.code,p.lpn_type,p.warehouse_id,p.location_id,p.operational_status,l.code location_code FROM $t p LEFT JOIN ".AKWMS_DB::table('locations')." l ON l.id=p.location_id WHERE p.code LIKE %s ORDER BY CASE WHEN p.code=%s THEN 0 WHEN p.code LIKE %s THEN 1 ELSE 2 END,p.id DESC LIMIT 30",$like,$q,$q.'%'),ARRAY_A);
        }
        return new WP_Error('bad_catalog','Catalog type must be sku or lpn',['status'=>400]);
    }
    public static function orders_list(WP_REST_Request $r) {
        global $wpdb;
        $page=max(1,(int)($r->get_param('page')?:1));
        $per=max(10,min(100,(int)($r->get_param('per_page')?:50)));
        $q=trim(sanitize_text_field($r->get_param('q')?:''));
        $status=strtoupper(sanitize_key($r->get_param('status')?:''));
        $orders=AKWMS_DB::table('orders'); $items=AKWMS_DB::table('order_items');
        $where=['1=1']; $args=[];
        if($q!==''){ $like='%'.$wpdb->esc_like($q).'%'; $where[]='(o.order_no LIKE %s OR o.external_order_id LIKE %s OR o.customer_name LIKE %s OR o.source_channel LIKE %s)'; array_push($args,$like,$like,$like,$like); }
        if($status!==''){ $where[]='o.status=%s'; $args[]=$status; }
        $whereSql=implode(' AND ',$where);
        $countSql="SELECT COUNT(*) FROM $orders o WHERE $whereSql";
        $total=(int)($args?$wpdb->get_var($wpdb->prepare($countSql,...$args)):$wpdb->get_var($countSql));
        $offset=($page-1)*$per;
        $sql="SELECT o.*,COALESCE(SUM(oi.ordered_qty),0) ordered_qty,COALESCE(SUM(oi.allocated_qty),0) allocated_qty FROM $orders o LEFT JOIN $items oi ON oi.order_id=o.id WHERE $whereSql GROUP BY o.id ORDER BY o.id DESC LIMIT %d OFFSET %d";
        $rows=$wpdb->get_results($wpdb->prepare($sql,...array_merge($args,[$per,$offset])),ARRAY_A);
        return ['rows'=>$rows,'total'=>$total,'page'=>$page,'per_page'=>$per,'pages'=>max(1,(int)ceil($total/$per))];
    }

    public static function inventory_list(WP_REST_Request $r) {
        global $wpdb;
        $page=max(1,(int)($r->get_param('page')?:1));
        $per=max(10,min(100,(int)($r->get_param('per_page')?:50)));
        $q=trim(sanitize_text_field($r->get_param('q')?:''));
        $inv=AKWMS_DB::table('inventory'); $skus=AKWMS_DB::table('skus'); $loc=AKWMS_DB::table('locations'); $wh=AKWMS_DB::table('warehouses'); $lpn=AKWMS_DB::table('lpns');
        $where=['i.on_hand<>0']; $args=[];
        if($q!==''){ $like='%'.$wpdb->esc_like($q).'%'; $where[]='(s.sku LIKE %s OR s.name LIKE %s OR l.code LIKE %s OR p.code LIKE %s OR i.batch_no LIKE %s)'; array_push($args,$like,$like,$like,$like,$like); }
        $whereSql=implode(' AND ',$where);
        $countSql="SELECT COUNT(*) FROM $inv i JOIN $skus s ON s.id=i.sku_id JOIN $loc l ON l.id=i.location_id LEFT JOIN $lpn p ON p.id=i.lpn_id WHERE $whereSql";
        $total=(int)($args?$wpdb->get_var($wpdb->prepare($countSql,...$args)):$wpdb->get_var($countSql));
        $offset=($page-1)*$per;
        $sql="SELECT i.id,i.warehouse_id,i.location_id,i.lpn_id,i.sku_id,i.batch_no,i.expiry_date,i.inventory_status,i.on_hand,i.reserved,GREATEST(i.on_hand-i.reserved,0) available_qty,s.sku,s.name sku_name,s.uom,w.code warehouse_code,l.code location_code,l.location_type,l.status location_status,l.is_allocatable,p.code lpn_code,p.operational_status lpn_status FROM $inv i JOIN $skus s ON s.id=i.sku_id JOIN $loc l ON l.id=i.location_id JOIN $wh w ON w.id=i.warehouse_id LEFT JOIN $lpn p ON p.id=i.lpn_id WHERE $whereSql ORDER BY i.id DESC LIMIT %d OFFSET %d";
        $rows=$wpdb->get_results($wpdb->prepare($sql,...array_merge($args,[$per,$offset])),ARRAY_A);
        return ['rows'=>$rows,'total'=>$total,'page'=>$page,'per_page'=>$per,'pages'=>max(1,(int)ceil($total/$per))];
    }

    public static function exceptions() {
        global $wpdb;
        $orders=AKWMS_DB::table('orders'); $tasks=AKWMS_DB::table('tasks'); $inv=AKWMS_DB::table('inventory'); $loc=AKWMS_DB::table('locations');
        return [
            'waiting_stock'=>(int)$wpdb->get_var("SELECT COUNT(*) FROM $orders WHERE status IN ('STOCK_EXCEPTION','BACKORDER')"),
            'missing_stock'=>(int)$wpdb->get_var("SELECT COUNT(*) FROM $tasks WHERE status='SHORT_PICK'"),
            'stuck_tasks'=>(int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $tasks WHERE status='IN_PROGRESS' AND started_at IS NOT NULL AND started_at < %s",date('Y-m-d H:i:s',current_time('timestamp')-4*HOUR_IN_SECONDS))),
            'stock_wrong_area'=>(int)$wpdb->get_var("SELECT COUNT(*) FROM $inv i JOIN $loc l ON l.id=i.location_id WHERE i.on_hand>0 AND (l.status<>'ACTIVE' OR l.is_allocatable=0)")
        ];
    }

    public static function cancel_order(WP_REST_Request $r) {
        global $wpdb;
        $oid=(int)$r['id'];
        $orders=AKWMS_DB::table('orders'); $items=AKWMS_DB::table('order_items'); $res=AKWMS_DB::table('reservations'); $inv=AKWMS_DB::table('inventory'); $wo=AKWMS_DB::table('wave_orders'); $tasks=AKWMS_DB::table('tasks'); $waves=AKWMS_DB::table('waves');
        $wpdb->query('START TRANSACTION');
        try {
            $order=$wpdb->get_row($wpdb->prepare("SELECT * FROM $orders WHERE id=%d FOR UPDATE",$oid),ARRAY_A);
            if(!$order) throw new Exception('Order not found');
            if($order['status']==='SHIPPED') throw new Exception('Shipped orders cannot be cancelled');
            if($order['status']==='CANCELLED'){ $wpdb->query('COMMIT'); return ['cancelled'=>true,'already'=>true]; }
            $waveIds=array_map('intval',$wpdb->get_col($wpdb->prepare("SELECT wave_id FROM $wo WHERE order_id=%d",$oid)));
            if($waveIds){
                $waveSql=implode(',',$waveIds);
                $mixed=(int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $wo WHERE wave_id IN ($waveSql) AND order_id<>%d",$oid));
                if($mixed>0) throw new Exception('This order is already inside a work batch with other orders. Finish or separate that warehouse work before cancelling.');
                $wpdb->query("UPDATE $tasks SET status='CANCELLED',completed_at='".esc_sql(current_time('mysql'))."' WHERE wave_id IN ($waveSql) AND status IN ('PENDING','IN_PROGRESS','SHORT_PICK')");
                $wpdb->query("UPDATE $waves SET status='CANCELLED',updated_at='".esc_sql(current_time('mysql'))."' WHERE id IN ($waveSql)");
            }
            $rows=$wpdb->get_results($wpdb->prepare("SELECT * FROM $res WHERE order_id=%d AND status='ACTIVE' FOR UPDATE",$oid),ARRAY_A);
            foreach($rows as $rv){
                $remaining=(float)$rv['quantity']-(float)$rv['consumed_qty'];
                if($remaining>0){$wpdb->query($wpdb->prepare("UPDATE $inv SET reserved=GREATEST(0,reserved-%f),updated_at=%s WHERE id=%d",$remaining,current_time('mysql'),(int)$rv['inventory_id']));}
                $wpdb->update($res,['status'=>'RELEASED','updated_at'=>current_time('mysql')],['id'=>(int)$rv['id']]);
            }
            $wpdb->update($items,['status'=>'CANCELLED'],['order_id'=>$oid]);
            $wpdb->update($orders,['status'=>'CANCELLED','updated_at'=>current_time('mysql')],['id'=>$oid]);
            $wpdb->query('COMMIT');
            AKWMS_Audit::log('order_cancelled','order',$oid,'Order cancelled from workspace',['released_reservations'=>count($rows),'waves'=>$waveIds]);
            return ['cancelled'=>true,'released_reservations'=>count($rows)];
        } catch(Throwable $e){
            $wpdb->query('ROLLBACK');
            return new WP_Error('cancel_failed',$e->getMessage(),['status'=>409]);
        }
    }
    public static function create_warehouse(WP_REST_Request $r) {
        global $wpdb; $d=$r->get_json_params();
        $code=strtoupper(trim(sanitize_text_field($d['code']??''))); $name=trim(sanitize_text_field($d['name']??''));
        if($code===''||$name==='') return new WP_Error('missing','Warehouse code and name are required',['status'=>400]);
        if($wpdb->get_var($wpdb->prepare("SELECT id FROM ".AKWMS_DB::table('warehouses')." WHERE code=%s",$code))) return new WP_Error('duplicate','Warehouse code already exists',['status'=>409]);
        $now=current_time('mysql');
        $ok=$wpdb->insert(AKWMS_DB::table('warehouses'),['code'=>$code,'name'=>$name,'address'=>sanitize_textarea_field($d['address']??''),'is_active'=>1,'created_at'=>$now,'updated_at'=>$now]);
        if(!$ok) return new WP_Error('db','Could not create warehouse',['status'=>500]);
        $id=(int)$wpdb->insert_id; AKWMS_Audit::log('warehouse_created','warehouse',$id,'Warehouse created from workspace',['code'=>$code]); return new WP_REST_Response(['id'=>$id],201);
    }

    public static function receive_commit(WP_REST_Request $r) {
        global $wpdb;
        $d=(array)$r->get_json_params();
        $warehouse_id=(int)($d['warehouse_id']??0); $location_id=(int)($d['location_id']??0); $qty=(float)($d['quantity']??0);
        $batch=sanitize_text_field($d['batch_no']??''); $expiry=!empty($d['expiry_date'])?sanitize_text_field($d['expiry_date']):null;
        $status=strtoupper(sanitize_key($d['inventory_status']??'AVAILABLE'));
        if(!$warehouse_id||!$location_id||$qty<=0) return new WP_Error('missing','Warehouse, location and a positive quantity are required',['status'=>400]);
        if(!in_array($status,['AVAILABLE','QUARANTINE','DAMAGED'],true)) return new WP_Error('bad_status','Invalid inventory status',['status'=>400]);
        $loc=$wpdb->get_row($wpdb->prepare("SELECT id,warehouse_id,status,code FROM ".AKWMS_DB::table('locations')." WHERE id=%d",$location_id),ARRAY_A);
        if(!$loc || (int)$loc['warehouse_id']!==$warehouse_id || $loc['status']!=='ACTIVE') return new WP_Error('bad_location','Choose an active location in the selected warehouse',['status'=>409]);
        $sku_id=(int)($d['sku_id']??0); $lpn_id=(int)($d['lpn_id']??0); $created_sku=null; $created_lpn=null;
        $new_sku=is_array($d['new_sku']??null)?$d['new_sku']:null; $new_lpn=is_array($d['new_lpn']??null)?$d['new_lpn']:null;
        if(!$sku_id && !$new_sku) return new WP_Error('sku_required','Select an existing product or stage a new SKU',['status'=>400]);
        if(($new_sku||$new_lpn) && !self::can_master()) return new WP_Error('forbidden_master','Creating a new SKU or pallet requires master-data permission',['status'=>403]);
        $now=current_time('mysql'); $wpdb->query('START TRANSACTION');
        try {
            if($sku_id){
                $sku=$wpdb->get_row($wpdb->prepare("SELECT id,sku,name FROM ".AKWMS_DB::table('skus')." WHERE id=%d AND is_active=1 FOR UPDATE",$sku_id),ARRAY_A);
                if(!$sku) throw new Exception('Selected SKU no longer exists or is inactive');
            } else {
                $code=strtoupper(trim(sanitize_text_field($new_sku['sku']??''))); $name=trim(sanitize_text_field($new_sku['name']??''));
                if($code===''||$name==='') throw new Exception('New SKU code and product name are required');
                $exists=$wpdb->get_var($wpdb->prepare("SELECT id FROM ".AKWMS_DB::table('skus')." WHERE sku=%s FOR UPDATE",$code));
                if($exists) throw new Exception('SKU '.$code.' already exists. Select the existing product instead.');
                $ok=$wpdb->insert(AKWMS_DB::table('skus'),['sku'=>$code,'name'=>$name,'barcode'=>sanitize_text_field($new_sku['barcode']??''),'uom'=>'EA','item_class'=>'NORMAL','allocation_strategy'=>strtoupper(sanitize_key($new_sku['allocation_strategy']??'FIFO')),'is_active'=>1,'created_at'=>$now,'updated_at'=>$now]);
                if(!$ok) throw new Exception('Could not create the new SKU');
                $sku_id=(int)$wpdb->insert_id; $sku=['id'=>$sku_id,'sku'=>$code,'name'=>$name]; $created_sku=$sku;
            }
            if($lpn_id){
                $lpn=$wpdb->get_row($wpdb->prepare("SELECT id,code,warehouse_id,location_id,operational_status FROM ".AKWMS_DB::table('lpns')." WHERE id=%d FOR UPDATE",$lpn_id),ARRAY_A);
                if(!$lpn || (int)$lpn['warehouse_id']!==$warehouse_id || $lpn['operational_status']!=='ACTIVE') throw new Exception('Selected pallet / LPN is not active in this warehouse');
                if(!empty($lpn['location_id']) && (int)$lpn['location_id']!==$location_id) throw new Exception('Selected pallet / LPN belongs to a different location. Choose the matching location or move the pallet first.');
            } elseif($new_lpn) {
                $code=strtoupper(trim(sanitize_text_field($new_lpn['code']??''))); if($code==='') throw new Exception('New pallet / LPN code is required');
                $exists=$wpdb->get_var($wpdb->prepare("SELECT id FROM ".AKWMS_DB::table('lpns')." WHERE code=%s FOR UPDATE",$code));
                if($exists) throw new Exception('Pallet / LPN '.$code.' already exists. Select the existing one instead.');
                $ok=$wpdb->insert(AKWMS_DB::table('lpns'),['code'=>$code,'lpn_type'=>'PALLET','warehouse_id'=>$warehouse_id,'location_id'=>$location_id,'operational_status'=>'ACTIVE','created_at'=>$now,'updated_at'=>$now]);
                if(!$ok) throw new Exception('Could not create the new pallet / LPN');
                $lpn_id=(int)$wpdb->insert_id; $created_lpn=['id'=>$lpn_id,'code'=>$code];
            }
            $inv=AKWMS_DB::table('inventory');
            $sql="SELECT * FROM $inv WHERE warehouse_id=%d AND location_id=%d AND sku_id=%d AND inventory_status=%s AND batch_no=%s AND ".($lpn_id?"lpn_id=%d":"lpn_id IS NULL")." AND ".($expiry?"expiry_date=%s":"expiry_date IS NULL")." FOR UPDATE";
            $params=[$warehouse_id,$location_id,$sku_id,$status,$batch]; if($lpn_id)$params[]=$lpn_id; if($expiry)$params[]=$expiry;
            $row=$wpdb->get_row($wpdb->prepare($sql,$params),ARRAY_A);
            if($row){$inventory_id=(int)$row['id'];$ok=$wpdb->update($inv,['on_hand'=>(float)$row['on_hand']+$qty,'updated_at'=>$now],['id'=>$inventory_id]);if($ok===false)throw new Exception('Could not update inventory');}
            else {$ok=$wpdb->insert($inv,['warehouse_id'=>$warehouse_id,'location_id'=>$location_id,'lpn_id'=>$lpn_id?:null,'sku_id'=>$sku_id,'batch_no'=>$batch,'expiry_date'=>$expiry,'inventory_status'=>$status,'on_hand'=>$qty,'reserved'=>0,'updated_at'=>$now]);if(!$ok)throw new Exception('Could not create inventory balance');$inventory_id=(int)$wpdb->insert_id;}
            $ok=$wpdb->insert(AKWMS_DB::table('inventory_transactions'),['txn_uuid'=>wp_generate_uuid4(),'transaction_type'=>'RECEIVE','warehouse_id'=>$warehouse_id,'sku_id'=>$sku_id,'lpn_id'=>$lpn_id?:null,'to_location_id'=>$location_id,'batch_no'=>$batch,'expiry_date'=>$expiry,'inventory_status'=>$status,'quantity'=>$qty,'reason'=>'RECEIVE','user_id'=>get_current_user_id()?:null,'created_at'=>$now]);
            if(!$ok) throw new Exception('Could not record receipt transaction');
            $wpdb->query('COMMIT');
            if($created_sku) AKWMS_Audit::log('sku_created','sku',$sku_id,'SKU created with stock receipt',['sku'=>$created_sku['sku']]);
            if($created_lpn) AKWMS_Audit::log('lpn_created','lpn',$lpn_id,'LPN created with stock receipt',['code'=>$created_lpn['code']]);
            AKWMS_Audit::log('receive','inventory',$inventory_id,'Inventory received',['qty'=>$qty,'sku_id'=>$sku_id,'lpn_id'=>$lpn_id?:null]);
            return ['inventory_id'=>$inventory_id,'sku_id'=>$sku_id,'lpn_id'=>$lpn_id?:null,'sku'=>$sku['sku']??'', 'sku_name'=>$sku['name']??'', 'location_code'=>$loc['code'],'lpn_code'=>$created_lpn['code']??($lpn['code']??null),'created_sku'=>(bool)$created_sku,'created_lpn'=>(bool)$created_lpn];
        } catch(Throwable $e) {
            $wpdb->query('ROLLBACK');
            return new WP_Error('receive_commit_failed',$e->getMessage(),['status'=>409]);
        }
    }
    public static function receipts() {
        global $wpdb;
        $tx=AKWMS_DB::table('inventory_transactions');
        $sql="SELECT t.id,t.created_at,t.quantity,t.batch_no,t.expiry_date,t.inventory_status,t.reason,s.sku,s.name sku_name,w.code warehouse_code,l.code location_code,p.code lpn_code,u.display_name user_name FROM $tx t LEFT JOIN ".AKWMS_DB::table('skus')." s ON s.id=t.sku_id LEFT JOIN ".AKWMS_DB::table('warehouses')." w ON w.id=t.warehouse_id LEFT JOIN ".AKWMS_DB::table('locations')." l ON l.id=t.to_location_id LEFT JOIN ".AKWMS_DB::table('lpns')." p ON p.id=t.lpn_id LEFT JOIN {$wpdb->users} u ON u.ID=t.user_id WHERE t.transaction_type='RECEIVE' ORDER BY t.id DESC LIMIT 25";
        return $wpdb->get_results($sql,ARRAY_A);
    }
    public static function create_sku(WP_REST_Request $r) {
        global $wpdb; $d=$r->get_json_params();
        $sku=strtoupper(trim(sanitize_text_field($d['sku']??''))); $name=trim(sanitize_text_field($d['name']??''));
        if($sku===''||$name==='') return new WP_Error('missing','SKU and name are required',['status'=>400]);
        if($wpdb->get_var($wpdb->prepare("SELECT id FROM ".AKWMS_DB::table('skus')." WHERE sku=%s",$sku))) return new WP_Error('duplicate','SKU already exists',['status'=>409]);
        $now=current_time('mysql');
        $ok=$wpdb->insert(AKWMS_DB::table('skus'),['sku'=>$sku,'name'=>$name,'barcode'=>sanitize_text_field($d['barcode']??''),'uom'=>strtoupper(sanitize_text_field($d['uom']??'EA')),'item_class'=>strtoupper(sanitize_key($d['item_class']??'NORMAL')),'allocation_strategy'=>strtoupper(sanitize_key($d['allocation_strategy']??'FIFO')),'is_active'=>1,'created_at'=>$now,'updated_at'=>$now]);
        if(!$ok) return new WP_Error('db','Could not create SKU',['status'=>500]);
        $id=(int)$wpdb->insert_id; AKWMS_Audit::log('sku_created','sku',$id,'SKU created from workspace',['sku'=>$sku]); return new WP_REST_Response(['id'=>$id],201);
    }

    public static function create_location(WP_REST_Request $r) {
        global $wpdb; $d=$r->get_json_params(); $wid=(int)($d['warehouse_id']??0); $code=strtoupper(trim(sanitize_text_field($d['code']??''))); $type=strtoupper(sanitize_key($d['location_type']??'STORAGE_BIN'));
        if(!$wid||$code==='') return new WP_Error('missing','Warehouse and location code are required',['status'=>400]);
        $valid=['STORAGE_BIN','RECEIVING_AREA','SORTING_STATION','PACKING_STATION','SHIPPING_STAGING','QUARANTINE_AREA','RETURN_AREA']; if(!in_array($type,$valid,true)) return new WP_Error('bad_type','Invalid location type',['status'=>400]);
        $alloc=$type==='STORAGE_BIN'?1:0; $now=current_time('mysql');
        $ok=$wpdb->insert(AKWMS_DB::table('locations'),['warehouse_id'=>$wid,'code'=>$code,'location_type'=>$type,'status'=>'ACTIVE','is_pickable'=>$alloc,'is_allocatable'=>$alloc,'created_at'=>$now,'updated_at'=>$now]);
        if(!$ok) return new WP_Error('db','Could not create location. Code may already exist.',['status'=>409]);
        $id=(int)$wpdb->insert_id; AKWMS_Audit::log('location_created','location',$id,'Location created from workspace',['code'=>$code]); return new WP_REST_Response(['id'=>$id],201);
    }

    public static function create_lpn(WP_REST_Request $r) {
        global $wpdb; $d=$r->get_json_params(); $wid=(int)($d['warehouse_id']??0); $code=strtoupper(trim(sanitize_text_field($d['code']??'')));
        if(!$wid||$code==='') return new WP_Error('missing','Warehouse and LPN code are required',['status'=>400]);
        $now=current_time('mysql'); $ok=$wpdb->insert(AKWMS_DB::table('lpns'),['code'=>$code,'lpn_type'=>strtoupper(sanitize_key($d['lpn_type']??'PALLET')),'warehouse_id'=>$wid,'location_id'=>!empty($d['location_id'])?(int)$d['location_id']:null,'operational_status'=>'ACTIVE','created_at'=>$now,'updated_at'=>$now]);
        if(!$ok) return new WP_Error('db','Could not create LPN. Code may already exist.',['status'=>409]);
        $id=(int)$wpdb->insert_id; AKWMS_Audit::log('lpn_created','lpn',$id,'LPN created from workspace',['code'=>$code]); return new WP_REST_Response(['id'=>$id],201);
    }

    public static function waves() {
        global $wpdb;
        $waves=$wpdb->get_results("SELECT w.*,wh.code warehouse_code,(SELECT COUNT(*) FROM ".AKWMS_DB::table('wave_orders')." wo WHERE wo.wave_id=w.id) order_count,(SELECT COUNT(*) FROM ".AKWMS_DB::table('tasks')." t WHERE t.wave_id=w.id) task_count,(SELECT COUNT(*) FROM ".AKWMS_DB::table('wave_orders')." two JOIN ".AKWMS_DB::table('orders')." tor ON tor.id=two.order_id WHERE two.wave_id=w.id AND tor.source_channel='MANUAL' AND tor.customer_name='Test Customer') test_order_count,(SELECT COUNT(*) FROM ".AKWMS_DB::table('tasks')." tc WHERE tc.wave_id=w.id AND tc.status='COMPLETED') completed_task_count FROM ".AKWMS_DB::table('waves')." w LEFT JOIN ".AKWMS_DB::table('warehouses')." wh ON wh.id=w.warehouse_id ORDER BY w.id DESC LIMIT 100",ARRAY_A);
        $allocated=$wpdb->get_results("SELECT id,order_no,source_channel,customer_name,priority,warehouse_id FROM ".AKWMS_DB::table('orders')." WHERE status='ALLOCATED' ORDER BY FIELD(priority,'URGENT','HIGH','NORMAL','LOW'),id DESC LIMIT 200",ARRAY_A);
        return ['waves'=>$waves,'allocated_orders'=>$allocated];
    }

    public static function create_wave(WP_REST_Request $r) {
        $d=$r->get_json_params(); $wid=(int)($d['warehouse_id']??0); $ids=array_values(array_filter(array_map('intval',(array)($d['order_ids']??[]))));
        if(!$wid||!$ids) return new WP_Error('missing','Warehouse and at least one allocated order are required',['status'=>400]);
        $x=AKWMS_Waves::create($wid,$ids); return is_wp_error($x)?$x:new WP_REST_Response(['id'=>$x],201);
    }
    public static function generate_wave(WP_REST_Request $r) { $x=AKWMS_Waves::generate_tasks((int)$r['id']); return is_wp_error($x)?$x:['generated'=>(int)$x]; }

    public static function reset_test(WP_REST_Request $r) {
        global $wpdb;
        $sku_id=(int)$wpdb->get_var($wpdb->prepare("SELECT id FROM ".AKWMS_DB::table('skus')." WHERE sku=%s",'TEST-SKU-001'));
        $lpn_id=(int)$wpdb->get_var($wpdb->prepare("SELECT id FROM ".AKWMS_DB::table('lpns')." WHERE code=%s",'PLT-TEST-001'));
        $orders=AKWMS_DB::table('orders'); $items=AKWMS_DB::table('order_items'); $wave_orders=AKWMS_DB::table('wave_orders');
        $order_ids=[];
        if($sku_id){
            $order_ids=array_map('intval',$wpdb->get_col($wpdb->prepare("SELECT DISTINCT o.id FROM $orders o JOIN $items oi ON oi.order_id=o.id WHERE o.source_channel=%s AND o.customer_name=%s AND oi.sku_id=%d",'MANUAL','Test Customer',$sku_id)));
            $test_order_sql=$order_ids?implode(',',$order_ids):'0';
            $other_usage=(int)$wpdb->get_var("SELECT COUNT(*) FROM $items WHERE sku_id=$sku_id AND order_id NOT IN ($test_order_sql)");
            if($other_usage>0) return new WP_Error('test_sku_in_use','Reset stopped: TEST-SKU-001 is also used by a non-test order.',['status'=>409]);
        }
        $wave_ids=[];
        if($order_ids){
            $order_sql=implode(',',$order_ids);
            $wave_ids=array_map('intval',$wpdb->get_col("SELECT DISTINCT wave_id FROM $wave_orders WHERE order_id IN ($order_sql)"));
            if($wave_ids){
                $wave_sql=implode(',',$wave_ids);
                $mixed=(int)$wpdb->get_var("SELECT COUNT(*) FROM $wave_orders WHERE wave_id IN ($wave_sql) AND order_id NOT IN ($order_sql)");
                if($mixed>0) return new WP_Error('mixed_wave','Reset stopped: a tutorial order is mixed into a work batch containing non-test orders.',['status'=>409]);
            }
        }
        $inventory_ids=$sku_id?array_map('intval',$wpdb->get_col("SELECT id FROM ".AKWMS_DB::table('inventory')." WHERE sku_id=$sku_id")):[];
        $wpdb->query('START TRANSACTION');
        try {
            if($order_ids){
                $order_sql=implode(',',$order_ids);
                $wpdb->query("DELETE FROM ".AKWMS_DB::table('reservations')." WHERE order_id IN ($order_sql)");
                $wpdb->query("DELETE FROM $items WHERE order_id IN ($order_sql)");
            }
            if($wave_ids){
                $wave_sql=implode(',',$wave_ids);
                $wpdb->query("DELETE FROM ".AKWMS_DB::table('tasks')." WHERE wave_id IN ($wave_sql)");
                $wpdb->query("DELETE FROM $wave_orders WHERE wave_id IN ($wave_sql)");
                $wpdb->query("DELETE FROM ".AKWMS_DB::table('waves')." WHERE id IN ($wave_sql)");
            }
            if($inventory_ids){
                $inventory_sql=implode(',',$inventory_ids);
                $wpdb->query("DELETE FROM ".AKWMS_DB::table('cycle_counts')." WHERE inventory_id IN ($inventory_sql)");
            }
            if($sku_id){
                $wpdb->query("DELETE FROM ".AKWMS_DB::table('inventory_transactions')." WHERE sku_id=$sku_id" . ($order_ids ? " OR order_id IN (".implode(',',$order_ids).")" : ''));
                $wpdb->query("DELETE FROM ".AKWMS_DB::table('inventory')." WHERE sku_id=$sku_id");
            }
            if($order_ids){ $wpdb->query("DELETE FROM $orders WHERE id IN (".implode(',',$order_ids).")"); }
            if($lpn_id){
                $lpn_used=(int)$wpdb->get_var("SELECT COUNT(*) FROM ".AKWMS_DB::table('inventory')." WHERE lpn_id=$lpn_id");
                if($lpn_used===0) $wpdb->delete(AKWMS_DB::table('lpns'),['id'=>$lpn_id]);
            }
            if($sku_id) $wpdb->delete(AKWMS_DB::table('skus'),['id'=>$sku_id]);
            $wpdb->query('COMMIT');
            AKWMS_Audit::log('tutorial_reset','workspace',null,'Tutorial simulation data reset',['sku'=>'TEST-SKU-001','lpn'=>'PLT-TEST-001','orders'=>$order_ids,'waves'=>$wave_ids]);
            return ['reset'=>true,'orders_removed'=>count($order_ids),'waves_removed'=>count($wave_ids),'inventory_rows_removed'=>count($inventory_ids),'kept'=>'WH01 and warehouse locations'];
        } catch(Throwable $e){
            $wpdb->query('ROLLBACK');
            return new WP_Error('reset_failed',$e->getMessage(),['status'=>409]);
        }
    }

    public static function workspace_tasks() {
        global $wpdb; $uid=get_current_user_id();
        $sql="SELECT t.*,s.sku,l1.code from_code,l2.code to_code,p.code lpn_code FROM ".AKWMS_DB::table('tasks')." t LEFT JOIN ".AKWMS_DB::table('skus')." s ON s.id=t.sku_id LEFT JOIN ".AKWMS_DB::table('locations')." l1 ON l1.id=t.from_location_id LEFT JOIN ".AKWMS_DB::table('locations')." l2 ON l2.id=t.to_location_id LEFT JOIN ".AKWMS_DB::table('lpns')." p ON p.id=t.lpn_id WHERE t.status IN ('PENDING','IN_PROGRESS','SHORT_PICK') AND (t.assigned_user_id IS NULL OR t.assigned_user_id=".(int)$uid.") ORDER BY FIELD(t.priority,'URGENT','HIGH','NORMAL','LOW'),t.id LIMIT 100";
        return $wpdb->get_results($sql,ARRAY_A);
    }

    public static function order_details(WP_REST_Request $r) {
        global $wpdb; $id=(int)$r['id'];
        $orders=AKWMS_DB::table('orders'); $itemsT=AKWMS_DB::table('order_items'); $resT=AKWMS_DB::table('reservations'); $invT=AKWMS_DB::table('inventory'); $locT=AKWMS_DB::table('locations'); $lpnT=AKWMS_DB::table('lpns'); $woT=AKWMS_DB::table('wave_orders'); $wavesT=AKWMS_DB::table('waves'); $tasksT=AKWMS_DB::table('tasks'); $auditT=AKWMS_DB::table('audit_log'); $txT=AKWMS_DB::table('inventory_transactions');
        $o=$wpdb->get_row($wpdb->prepare("SELECT * FROM $orders WHERE id=%d",$id),ARRAY_A); if(!$o) return new WP_Error('not_found','Order not found',['status'=>404]);
        $items=$wpdb->get_results($wpdb->prepare("SELECT oi.*,s.sku,s.name FROM $itemsT oi JOIN ".AKWMS_DB::table('skus')." s ON s.id=oi.sku_id WHERE oi.order_id=%d ORDER BY oi.id",$id),ARRAY_A);
        $reservations=$wpdb->get_results($wpdb->prepare("SELECT r.*,i.on_hand,i.reserved,l.code location_code,p.code lpn_code,s.sku FROM $resT r LEFT JOIN $invT i ON i.id=r.inventory_id LEFT JOIN $locT l ON l.id=i.location_id LEFT JOIN $lpnT p ON p.id=i.lpn_id LEFT JOIN ".AKWMS_DB::table('skus')." s ON s.id=i.sku_id WHERE r.order_id=%d ORDER BY r.id",$id),ARRAY_A);
        $waves=$wpdb->get_results($wpdb->prepare("SELECT w.id,w.wave_no,w.status,w.created_at,w.updated_at FROM $woT wo JOIN $wavesT w ON w.id=wo.wave_id WHERE wo.order_id=%d ORDER BY w.id",$id),ARRAY_A);
        $tasks=$wpdb->get_results($wpdb->prepare("SELECT DISTINCT t.id,t.task_no,t.task_type,t.status,t.requested_qty,t.confirmed_qty,t.started_at,t.completed_at,s.sku,l.code from_code,p.code lpn_code FROM $woT wo JOIN $tasksT t ON t.wave_id=wo.wave_id LEFT JOIN ".AKWMS_DB::table('skus')." s ON s.id=t.sku_id LEFT JOIN $locT l ON l.id=t.from_location_id LEFT JOIN $lpnT p ON p.id=t.lpn_id WHERE wo.order_id=%d ORDER BY t.id",$id),ARRAY_A);
        $audit=$wpdb->get_results($wpdb->prepare("SELECT a.event_type,a.message,a.payload,a.created_at,u.display_name user_name FROM $auditT a LEFT JOIN {$wpdb->users} u ON u.ID=a.user_id WHERE a.entity_type='order' AND a.entity_id=%s ORDER BY a.id DESC LIMIT 100",(string)$id),ARRAY_A);
        $txns=$wpdb->get_results($wpdb->prepare("SELECT t.transaction_type,t.quantity,t.reason,t.created_at,s.sku,l1.code from_code,l2.code to_code FROM $txT t LEFT JOIN ".AKWMS_DB::table('skus')." s ON s.id=t.sku_id LEFT JOIN $locT l1 ON l1.id=t.from_location_id LEFT JOIN $locT l2 ON l2.id=t.to_location_id WHERE t.order_id=%d ORDER BY t.id DESC LIMIT 100",$id),ARRAY_A);
        return ['order'=>$o,'items'=>$items,'reservations'=>$reservations,'waves'=>$waves,'tasks'=>$tasks,'audit'=>$audit,'transactions'=>$txns];
    }
    public static function ship_order(WP_REST_Request $r) {
        global $wpdb; $oid=(int)$r['id']; $orders=AKWMS_DB::table('orders'); $items=AKWMS_DB::table('order_items'); $res=AKWMS_DB::table('reservations'); $inv=AKWMS_DB::table('inventory');
        $order=$wpdb->get_row($wpdb->prepare("SELECT * FROM $orders WHERE id=%d",$oid),ARRAY_A); if(!$order) return new WP_Error('not_found','Order not found',['status'=>404]);
        if($order['status']==='SHIPPED') return ['shipped'=>true,'already'=>true];
        if($order['status']!=='PACKED') return new WP_Error('bad_status','Pack and verify the order before shipping',['status'=>409]);
        $open_tasks=(int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM ".AKWMS_DB::table('tasks')." t JOIN ".AKWMS_DB::table('wave_orders')." wo ON wo.wave_id=t.wave_id WHERE wo.order_id=%d AND t.status IN ('PENDING','IN_PROGRESS','SHORT_PICK')",$oid));
        if($open_tasks>0) return new WP_Error('tasks_open','Complete or resolve all warehouse tasks for this order before shipping',['status'=>409]);
        $wpdb->query('START TRANSACTION');
        try {
            $rows=$wpdb->get_results($wpdb->prepare("SELECT * FROM $res WHERE order_id=%d AND status='ACTIVE' FOR UPDATE",$oid),ARRAY_A); if(!$rows) throw new Exception('Order has no active stock reservations');
            foreach($rows as $rv){ $qty=(float)$rv['quantity']-(float)$rv['consumed_qty']; if($qty<=0) continue; $bucket=$wpdb->get_row($wpdb->prepare("SELECT * FROM $inv WHERE id=%d FOR UPDATE",(int)$rv['inventory_id']),ARRAY_A); if(!$bucket) throw new Exception('Reserved inventory bucket missing'); if((float)$bucket['on_hand']<$qty||(float)$bucket['reserved']<$qty) throw new Exception('Reserved stock is no longer available');
                $wpdb->update($inv,['on_hand'=>(float)$bucket['on_hand']-$qty,'reserved'=>(float)$bucket['reserved']-$qty,'updated_at'=>current_time('mysql')],['id'=>(int)$bucket['id']]);
                $wpdb->update($res,['consumed_qty'=>(float)$rv['quantity'],'status'=>'CONSUMED','updated_at'=>current_time('mysql')],['id'=>(int)$rv['id']]);
                $wpdb->insert(AKWMS_DB::table('inventory_transactions'),['txn_uuid'=>wp_generate_uuid4(),'transaction_type'=>'SHIP','order_id'=>$oid,'warehouse_id'=>(int)$bucket['warehouse_id'],'sku_id'=>(int)$bucket['sku_id'],'lpn_id'=>$bucket['lpn_id']?:null,'from_location_id'=>(int)$bucket['location_id'],'to_location_id'=>null,'batch_no'=>$bucket['batch_no'],'expiry_date'=>$bucket['expiry_date']?:null,'inventory_status'=>$bucket['inventory_status'],'quantity'=>-$qty,'reason'=>'ORDER_SHIPPED','user_id'=>get_current_user_id()?:null,'created_at'=>current_time('mysql')]);
            }
            $wpdb->query($wpdb->prepare("UPDATE $items SET picked_qty=ordered_qty,packed_qty=ordered_qty,shipped_qty=ordered_qty,status='SHIPPED' WHERE order_id=%d",$oid));
            $wpdb->update($orders,['status'=>'SHIPPED','updated_at'=>current_time('mysql')],['id'=>$oid]);
            $wpdb->query('COMMIT'); AKWMS_Audit::log('order_shipped','order',$oid,'Order shipped from workspace'); do_action('akwms_order_shipped',$oid); return ['shipped'=>true];
        } catch(Throwable $e){ $wpdb->query('ROLLBACK'); return new WP_Error('ship_failed',$e->getMessage(),['status'=>409]); }
    }

    public static function shortcode(): string {
        if (!is_user_logged_in()) return '<div class="akwms-shell"><div class="akwms-access"><h1>WMS Workspace</h1><p>Sign in with an authorized warehouse account.</p><a class="akwms-btn" href="'.esc_url(wp_login_url(get_permalink())).'">Sign in</a></div></div>';
        if (!self::can_view()) return '<div class="akwms-shell"><div class="akwms-access"><h1>WMS Workspace</h1><p>Your account does not have WMS access.</p></div></div>';
        $css_path = WP_CONTENT_DIR . '/novamira-sandbox/wms-workspace-pro.css';
        $js_path = WP_CONTENT_DIR . '/novamira-sandbox/wms-workspace-pro.js';
        wp_enqueue_style('akwms-workspace-pro', content_url('novamira-sandbox/wms-workspace-pro.css'), [], file_exists($css_path) ? (string) filemtime($css_path) : '1.0.1');
        wp_enqueue_script('akwms-workspace-pro', content_url('novamira-sandbox/wms-workspace-pro.js'), ['wp-api-fetch'], file_exists($js_path) ? (string) filemtime($js_path) : '1.0.1', true);
        wp_localize_script('akwms-workspace-pro','AKWMS_PRO',['root'=>esc_url_raw(rest_url('wms/v1/')),'nonce'=>wp_create_nonce('wp_rest')]);
        ob_start(); ?>
<div id="akwms-pro" class="akwms-shell">
<header class="akwms-top"><div><p class="akwms-kicker">Warehouse Operations</p><h1>WMS Workspace</h1><p class="akwms-lead">Run receiving, inventory, orders, waves and fulfillment from one workspace.</p></div></header>
<nav class="akwms-nav" aria-label="WMS workspace"><button class="is-active" data-tab="overview">Overview</button><button data-tab="setup">Setup</button><button data-tab="inventory">Inventory</button><button data-tab="receive">Receive</button><button data-tab="putaway">Putaway</button><button data-tab="orders">Orders</button><button data-tab="waves">Waves</button><button data-tab="tasks">Tasks</button><button data-tab="packing">Pack</button><button data-tab="count">Count</button><button data-tab="returns">Returns</button><button data-tab="integrations">Integrations</button><span class="akwms-nav-spacer"></span><button type="button" class="akwms-guide-toggle" data-guide-toggle aria-pressed="true">Guided Mode: On</button></nav>
<div id="akwms-msg" class="akwms-msg" role="status"></div>
<section id="akwms-guide" class="akwms-guide" aria-live="polite"></section>
<section class="akwms-pane is-active" data-pane="overview"><div id="akwms-metrics" class="akwms-metrics"></div><div class="akwms-panel"><div class="akwms-section-head"><div><h2>Needs attention</h2><p>Problems that should be checked before they block warehouse work.</p></div></div><div id="akwms-exceptions" class="akwms-exception-grid"></div></div><div class="akwms-panel"><div class="akwms-section-head"><div><h2>Operational queue</h2><p>Open warehouse tasks requiring action.</p></div></div><div id="akwms-overview-tasks"></div></div></section>
<section class="akwms-pane" data-pane="setup"><div class="akwms-section-head"><div><h2>Master data</h2><p>Create warehouses, SKUs, locations and pallets needed to operate.</p></div></div><div class="akwms-grid-master"><form id="akwms-warehouse-form" class="akwms-panel akwms-form"><h3>Add warehouse</h3><label>Warehouse code<input name="code" required placeholder="WH02"></label><label>Warehouse name<input name="name" required placeholder="Second Warehouse"></label><label>Address<textarea name="address" rows="3" placeholder="Optional"></textarea></label><button class="akwms-btn">Create warehouse</button></form><form id="akwms-sku-form" class="akwms-panel akwms-form"><h3>Add SKU</h3><label>SKU<input name="sku" required></label><label>Name<input name="name" required></label><label>Barcode<input name="barcode"></label><label>Stock picking rule<select name="allocation_strategy"><option>FIFO</option><option>FEFO</option><option>MANUAL</option></select></label><button class="akwms-btn">Create SKU</button></form><form id="akwms-location-form" class="akwms-panel akwms-form"><h3>Add location</h3><label>Warehouse<select name="warehouse_id" data-options="warehouses" required></select></label><label>Code<input name="code" required placeholder="STOR-A02"></label><label>Type<select name="location_type"><option value="STORAGE_BIN">Storage bin — normal stock storage</option><option value="RECEIVING_AREA">Receiving area — incoming stock</option><option value="SORTING_STATION">Sorting station — sort picked items</option><option value="PACKING_STATION">Packing station — pack orders</option><option value="SHIPPING_STAGING">Shipping staging — ready to dispatch</option><option value="QUARANTINE_AREA">Quarantine area — hold blocked stock</option><option value="RETURN_AREA">Return area — returned items</option></select></label><small class="akwms-field-help">Only Storage bin locations are used for normal stock reservation and picking.</small><button class="akwms-btn">Create location</button></form><form id="akwms-lpn-form" class="akwms-panel akwms-form"><h3>Add pallet / LPN</h3><label>Code<input name="code" required placeholder="PLT-000001"></label><label>Type<select name="lpn_type"><option value="PALLET">Pallet</option><option value="TOTE">Tote</option><option value="CARTON">Carton</option><option value="BOX">Box</option></select></label><label>Warehouse<select name="warehouse_id" data-options="warehouses" required></select></label><label>Location<select name="location_id" data-options="locations"><option value="">No location</option></select></label><button class="akwms-btn">Create LPN</button></form></div><div class="akwms-panel"><h3>Current setup</h3><div id="akwms-setup-summary" class="akwms-summary"></div></div></section>
<section class="akwms-pane" data-pane="inventory"><div class="akwms-section-head"><div><h2>Inventory</h2><p>Search live stock by SKU, product, pallet, batch or location.</p></div><input id="akwms-inv-search" type="search" placeholder="Search inventory"></div><div id="akwms-inventory"></div></section>
<section class="akwms-pane" data-pane="receive"><div class="akwms-section-head"><div><h2>Receive stock</h2><p>Put physical stock into a warehouse location.</p></div></div><form id="akwms-receive-form" class="akwms-panel akwms-form-grid"><label>Warehouse<select name="warehouse_id" data-options="warehouses" required></select></label><label>Location<select name="location_id" data-options="locations" required></select></label><label class="akwms-receive-combo">Product / SKU<div class="akwms-combo" data-receive-combo="sku"><input type="search" class="akwms-combo-input" autocomplete="off" placeholder="Search or enter new SKU"><input type="hidden" name="sku_id" class="akwms-combo-id" required><div class="akwms-combo-results" hidden></div></div></label><label class="akwms-receive-combo">Pallet / LPN <span class="akwms-optional">optional</span><div class="akwms-combo" data-receive-combo="lpn"><input type="search" class="akwms-combo-input" autocomplete="off" placeholder="Search or enter new pallet / LPN"><input type="hidden" name="lpn_id" class="akwms-combo-id"><div class="akwms-combo-results" hidden></div></div></label><label>Quantity<input name="quantity" type="number" min="0.0001" step="0.0001" required></label><label>Batch<input name="batch_no"></label><label>Expiry<input name="expiry_date" type="date"></label><label>Status<select name="inventory_status"><option>AVAILABLE</option><option>QUARANTINE</option><option>DAMAGED</option></select></label><div class="akwms-actions"><button class="akwms-btn">Record receipt</button></div></form><div class="akwms-section-head akwms-receipts-head"><div><h2>Recent receipts</h2><p>See exactly what was just added to stock.</p></div></div><div id="akwms-receipts"></div></section>
<section class="akwms-pane" data-pane="orders"><div class="akwms-section-head"><div><h2>Orders</h2><p>Create orders, reserve stock and ship customer orders.</p></div><input id="akwms-order-search" type="search" placeholder="Search orders"></div><details class="akwms-panel akwms-create-order"><summary>Create manual order</summary><form id="akwms-order-form" class="akwms-form-grid"><label>Channel<input name="source_channel" value="MANUAL"></label><label>External ID<input name="external_order_id" placeholder="Optional"></label><label>Customer<input name="customer_name"></label><label>Priority<select name="priority"><option>NORMAL</option><option>HIGH</option><option>URGENT</option><option>LOW</option></select></label><label>Warehouse<select name="warehouse_id" data-options="warehouses"><option value="">Auto</option></select></label><div class="akwms-span akwms-order-lines-block"><div class="akwms-line-head"><strong>Order lines</strong><button type="button" class="akwms-btn akwms-secondary akwms-add-line-btn" data-action="add-line">+ Add line</button></div><div class="akwms-order-line-head" aria-hidden="true"><span>Product / SKU</span><span>Quantity</span><span></span></div><div id="akwms-order-lines"></div></div><div class="akwms-span akwms-order-submit"><span id="akwms-order-line-count" class="akwms-field-help"></span><button class="akwms-btn">Create order</button></div></form></details><div id="akwms-orders"></div></section>
<section class="akwms-pane" data-pane="waves"><div class="akwms-section-head"><div><h2>Waves</h2><p>Turn reserved orders into warehouse work.</p></div></div><form id="akwms-wave-form" class="akwms-panel akwms-form"><label>Warehouse<select name="warehouse_id" data-options="warehouses" required></select></label><div><strong>Reserved orders</strong><div id="akwms-wave-orders" class="akwms-checklist"></div></div><button class="akwms-btn">Create wave</button></form><div id="akwms-waves"></div></section>
<section class="akwms-pane" data-pane="tasks"><div class="akwms-section-head"><div><h2>Warehouse tasks</h2><p>Start, complete or report a short pick.</p></div></div><div id="akwms-tasks"></div></section><section class="akwms-pane" data-pane="putaway"><div class="akwms-section-head"><div><h2>Putaway</h2><p>Move newly received stock from the receiving area into normal storage.</p></div></div><div id="akwms-putaway"><div class="akwms-empty">Open this tab to load stock waiting for putaway.</div></div></section>
<section class="akwms-pane" data-pane="packing"><div class="akwms-section-head"><div><h2>Pack orders</h2><p>Verify picked orders before they are allowed to ship.</p></div></div><div id="akwms-packing"><div class="akwms-empty">Open this tab to load orders ready for packing.</div></div></section>
<section class="akwms-pane" data-pane="count"><div class="akwms-section-head"><div><h2>Stock count</h2><p>Compare physical stock with the WMS and record any difference safely.</p></div></div><div id="akwms-counts"><div class="akwms-empty">Open this tab to load stock counts.</div></div></section>
<section class="akwms-pane" data-pane="returns"><div class="akwms-section-head"><div><h2>Returns</h2><p>Record customer returns against shipped orders and decide what happens to the stock.</p></div></div><div class="akwms-panel"><form id="akwms-return-form" class="akwms-form-grid"><label>Shipped order<select name="order_id" id="akwms-return-order" required><option value="">Choose shipped order</option></select></label><label>Product / SKU<select name="sku_id" id="akwms-return-sku" required><option value="">Choose order first</option></select></label><label>Quantity<input name="quantity" type="number" min="0.0001" step="0.0001" required></label><label>Result<select name="disposition"><option value="QUARANTINE">Hold for inspection</option><option value="RESTOCK">Return to available stock</option><option value="DAMAGED">Damaged — do not sell</option></select></label><label>Reason<select name="reason"><option value="CUSTOMER_RETURN">Customer return</option><option value="WRONG_ITEM">Wrong item</option><option value="DAMAGED_IN_TRANSIT">Damaged in transit</option><option value="UNWANTED">Customer did not want item</option><option value="FAILED_DELIVERY">Failed delivery</option><option value="OTHER">Other</option></select></label><label class="akwms-span">Notes<textarea name="notes" rows="2" placeholder="Optional"></textarea></label><div class="akwms-span akwms-actions"><button class="akwms-btn">Record return</button></div></form></div><div id="akwms-returns"><div class="akwms-empty">Open this tab to load return history.</div></div></section>
<section class="akwms-pane" data-pane="integrations"><div class="akwms-section-head"><div><h2>Integrations</h2><p>Marketplace connectors are prepared but remain disconnected until seller API credentials are supplied.</p></div></div><div id="akwms-integrations"><div class="akwms-empty">Open this tab to check integration readiness.</div></div></section>
</div><?php return (string)ob_get_clean();
    }
}
AKWMS_Workspace_Pro::init();

Youez - 2016 - github.com/yon3zu
LinuXploit