| 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 : |
<?php
/** Procedure engine and documented core operations for the SiteGiant WMS workspace. */
if (!defined('ABSPATH')) { return; }
final class AKWMS_SiteGiant_Procedures {
const OPT_RUNS='akwms_sitegiant_procedure_runs';
const OPT_CONTAINERS='akwms_sitegiant_containers';
public static function init(): void {
add_action('rest_api_init',[__CLASS__,'routes'],100);
add_action('wp_enqueue_scripts',[__CLASS__,'assets'],100);
}
public static function can_view(): bool { return current_user_can('akwms_view') || current_user_can('manage_options'); }
public static function can_manage(): bool { return current_user_can('akwms_manage_waves') || current_user_can('akwms_manage_master') || current_user_can('manage_options'); }
public static function assets(): void {
if (!is_page('wms-workspace-sitegiant')) return;
$p=WP_CONTENT_DIR.'/novamira-sandbox/wms-sitegiant-procedures.js';
wp_enqueue_script('akwms-sitegiant-procedures',content_url('novamira-sandbox/wms-sitegiant-procedures.js'),['akwms-sitegiant'],file_exists($p)?(string)filemtime($p):'1',true);
}
public static function routes(): void {
$ns='wms-sitegiant/v1';
register_rest_route($ns,'/procedures',['methods'=>'GET','callback'=>[__CLASS__,'procedures'],'permission_callback'=>[__CLASS__,'can_view']]);
register_rest_route($ns,'/procedure-runs',['methods'=>'GET','callback'=>[__CLASS__,'runs'],'permission_callback'=>[__CLASS__,'can_view']]);
register_rest_route($ns,'/procedure-runs',['methods'=>'POST','callback'=>[__CLASS__,'create_run'],'permission_callback'=>[__CLASS__,'can_manage']]);
register_rest_route($ns,'/procedure-runs/(?P<id>[A-Za-z0-9_-]+)',['methods'=>'POST','callback'=>[__CLASS__,'update_run'],'permission_callback'=>[__CLASS__,'can_manage']]);
register_rest_route($ns,'/containers',['methods'=>'GET','callback'=>[__CLASS__,'containers'],'permission_callback'=>[__CLASS__,'can_view']]);
register_rest_route($ns,'/containers',['methods'=>'POST','callback'=>[__CLASS__,'save_container'],'permission_callback'=>[__CLASS__,'can_manage']]);
register_rest_route($ns,'/core/move',['methods'=>'POST','callback'=>[__CLASS__,'move_stock'],'permission_callback'=>[__CLASS__,'can_manage']]);
register_rest_route($ns,'/core/adjust',['methods'=>'POST','callback'=>[__CLASS__,'adjust_stock'],'permission_callback'=>[__CLASS__,'can_manage']]);
register_rest_route($ns,'/core/count',['methods'=>'POST','callback'=>[__CLASS__,'count_stock'],'permission_callback'=>[__CLASS__,'can_manage']]);
register_rest_route($ns,'/core/inventory',['methods'=>'GET','callback'=>[__CLASS__,'inventory'],'permission_callback'=>[__CLASS__,'can_view']]);
}
private static function manifest(): array {
$f=WP_CONTENT_DIR.'/novamira-sandbox/wms-sitegiant-procedures.json';
if(!is_readable($f)) return [];
$d=json_decode((string)file_get_contents($f),true);
return is_array($d)?$d:[];
}
public static function procedures(){ return self::manifest(); }
private static function all_runs(): array { return (array)get_option(self::OPT_RUNS,[]); }
public static function runs(){ return array_values(self::all_runs()); }
private static function find_proc(string $id): ?array { foreach(self::manifest() as $p) if(($p['id']??'')===$id)return $p; return null; }
public static function create_run(WP_REST_Request $r){
$d=(array)$r->get_json_params(); $pid=sanitize_key($d['procedure_id']??''); $p=self::find_proc($pid);
if(!$p)return new WP_Error('bad_procedure','Unknown SiteGiant procedure',['status'=>400]);
$id='SGR-'.gmdate('YmdHis').'-'.wp_rand(100,999); $runs=self::all_runs();
$runs[$id]=['id'=>$id,'procedure_id'=>$pid,'title'=>$p['title'],'category'=>$p['category'],'current_step'=>0,'status'=>'IN_PROGRESS','notes'=>sanitize_textarea_field($d['notes']??''),'created_by'=>get_current_user_id(),'created_at'=>current_time('mysql'),'updated_at'=>current_time('mysql')];
update_option(self::OPT_RUNS,$runs,false); return new WP_REST_Response($runs[$id],201);
}
public static function update_run(WP_REST_Request $r){
$id=sanitize_text_field($r['id']); $runs=self::all_runs(); if(!isset($runs[$id]))return new WP_Error('not_found','Procedure run not found',['status'=>404]);
$d=(array)$r->get_json_params(); $run=$runs[$id]; $p=self::find_proc($run['procedure_id']); if(!$p)return new WP_Error('bad_procedure','Procedure definition missing',['status'=>409]);
if(isset($d['action']) && $d['action']==='complete_step'){
$max=max(0,count($p['stages'])-1); $cur=(int)$run['current_step'];
if($cur<$max)$run['current_step']=$cur+1; else {$run['status']='COMPLETED';$run['completed_at']=current_time('mysql');}
}
if(isset($d['notes']))$run['notes']=sanitize_textarea_field($d['notes']);
if(isset($d['action']) && $d['action']==='cancel')$run['status']='CANCELLED';
$run['updated_at']=current_time('mysql'); $runs[$id]=$run; update_option(self::OPT_RUNS,$runs,false); return $run;
}
private static function all_containers(): array { return (array)get_option(self::OPT_CONTAINERS,[]); }
public static function containers(){ return array_values(self::all_containers()); }
public static function save_container(WP_REST_Request $r){
$d=(array)$r->get_json_params(); $code=strtoupper(trim(sanitize_text_field($d['code']??''))); $type=strtoupper(sanitize_key($d['type']??'TROLLEY'));
if($code==='')return new WP_Error('code_required','Container code is required',['status'=>400]); if(!in_array($type,['TROLLEY','PICKING_BIN'],true))return new WP_Error('bad_type','Type must be trolley or picking bin',['status'=>400]);
$all=self::all_containers(); foreach($all as $x)if(($x['code']??'')===$code)return new WP_Error('duplicate','Container code already exists',['status'=>409]);
$id=sanitize_key($code); $all[$id]=['id'=>$id,'code'=>$code,'type'=>$type,'bin_count'=>max(1,(int)($d['bin_count']??1)),'status'=>'ACTIVE','created_at'=>current_time('mysql')]; update_option(self::OPT_CONTAINERS,$all,false); return new WP_REST_Response($all[$id],201);
}
public static function inventory(WP_REST_Request $r){
global $wpdb; if(!class_exists('AKWMS_DB'))return [];
$q=trim(sanitize_text_field($r->get_param('q')?:'')); $like='%'.$wpdb->esc_like($q).'%';
$sql="SELECT i.id inventory_id,i.on_hand,i.reserved,i.inventory_status,s.sku,s.name sku_name,w.code warehouse_code,l.id location_id,l.code location_code,l.location_type,p.code lpn_code FROM ".AKWMS_DB::table('inventory')." 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";
if($q!=='')$sql=$wpdb->prepare($sql." AND (s.sku LIKE %s OR s.name LIKE %s OR l.code LIKE %s OR p.code LIKE %s)",$like,$like,$like,$like);
$rows=$wpdb->get_results($sql." ORDER BY s.sku,l.code LIMIT 100",ARRAY_A);
$locations=$wpdb->get_results("SELECT l.id,l.warehouse_id,l.code,l.location_type,l.status,w.code warehouse_code FROM ".AKWMS_DB::table('locations')." l LEFT JOIN ".AKWMS_DB::table('warehouses')." w ON w.id=l.warehouse_id WHERE l.status='ACTIVE' ORDER BY w.code,l.code",ARRAY_A);
return ['rows'=>$rows,'locations'=>$locations];
}
public static function move_stock(WP_REST_Request $r){
if(!class_exists('AKWMS_Inventory'))return new WP_Error('wms_missing','WMS inventory engine unavailable',['status'=>503]); $d=(array)$r->get_json_params();
$iid=(int)($d['inventory_id']??0);$loc=(int)($d['to_location_id']??0);$qty=(float)($d['quantity']??0); if(!$iid||!$loc||$qty<=0)return new WP_Error('missing','Inventory row, destination rack and positive quantity are required',['status'=>400]);
$reason=strtoupper(sanitize_key($d['reason']??'SITEGIANT_TRANSFER')); $x=AKWMS_Inventory::move($iid,$loc,$qty,$reason); return is_wp_error($x)?$x:['moved'=>(bool)$x];
}
public static function adjust_stock(WP_REST_Request $r){
if(!class_exists('AKWMS_Inventory'))return new WP_Error('wms_missing','WMS inventory engine unavailable',['status'=>503]); $d=(array)$r->get_json_params();
$iid=(int)($d['inventory_id']??0);$qty=(float)($d['new_quantity']??-1); if(!$iid||$qty<0)return new WP_Error('missing','Inventory row and non-negative physical quantity are required',['status'=>400]);
$reason=strtoupper(sanitize_key($d['reason']??'SITEGIANT_STOCK_ADJUSTMENT')); $x=AKWMS_Inventory::adjust($iid,$qty,$reason); return is_wp_error($x)?$x:['adjusted'=>(bool)$x];
}
public static function count_stock(WP_REST_Request $r){
if(!class_exists('AKWMS_Inventory'))return new WP_Error('wms_missing','WMS inventory engine unavailable',['status'=>503]); $d=(array)$r->get_json_params();
$iid=(int)($d['inventory_id']??0);$qty=(float)($d['counted_quantity']??-1); if(!$iid||$qty<0)return new WP_Error('missing','Inventory row and counted quantity are required',['status'=>400]);
$cid=AKWMS_Inventory::create_cycle_count($iid,'SITEGIANT_STOCK_CHECK'); if(is_wp_error($cid))return $cid; $x=AKWMS_Inventory::complete_cycle_count((int)$cid,$qty,'COUNT_VARIANCE'); return is_wp_error($x)?$x:['count_id'=>(int)$cid,'completed'=>(bool)$x];
}
}
AKWMS_SiteGiant_Procedures::init();