StatTreeLineController.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Models\Clause;
  4. use App\Models\StatTreeLineClause;
  5. use Illuminate\Http\Request;
  6. use App\Models\StatTree;
  7. use App\Models\StatTreeLine;
  8. use App\Models\Client;
  9. use App\Models\Pro;
  10. use Illuminate\Pagination\LengthAwarePaginator;
  11. use Illuminate\Support\Facades\DB;
  12. use Ramsey\Uuid\Uuid;
  13. class StatTreeLineController extends Controller
  14. {
  15. public function list()
  16. {
  17. $statTreeLines = StatTreeLine::all();
  18. return view('app.stat-tree-lines.list', compact('statTreeLines'));
  19. }
  20. public function dashboard(StatTreeLine $statTreeLine)
  21. {
  22. $result = $this->applyStatTreeLineQueryClauses($statTreeLine);
  23. if (!$result) {
  24. $response = 'Invalid query or model/table name';
  25. } else {
  26. $response = [
  27. 'type' => 'count',
  28. 'sql' => $result->toSql(),
  29. 'result' => $result->count()
  30. ];
  31. }
  32. return view('app.stat-tree.stat-tree-lines.single', compact('statTreeLine', 'response'));
  33. }
  34. public function columnSuggest(Request $request) {
  35. $term = $request->input('term') ? trim($request->input('term')) : '';
  36. if (empty($term)) return '';
  37. $table = $request->input('table') ? trim($request->input('table')) : '';
  38. if (empty($table)) return '';
  39. $table = strtolower($table);
  40. $tables = [];
  41. $columns = [];
  42. // if single-term without spaces, single table
  43. if(strpos($table, ' ') === FALSE) {
  44. $tables[] = $table;
  45. }
  46. else {
  47. $table = explode(" ", $table);
  48. $tables[] = $table[0];
  49. for ($i = 1; $i < count($table); $i++) {
  50. if($table[$i] == 'join' && $i < count($table) - 1) {
  51. $tables[] = $table[$i+1];
  52. $i++;
  53. }
  54. }
  55. }
  56. for ($i = 0; $i < count($tables); $i++) {
  57. $cols = DB::getSchemaBuilder()->getColumnListing($tables[$i]);
  58. sort($cols);
  59. $matches = array_filter($cols, function($_x) use ($term) {
  60. return strpos($_x, $term) !== FALSE;
  61. });
  62. $matches = array_map(function($_x) use ($tables, $i) {
  63. return [
  64. "text" => $tables[$i] . '.' . $_x,
  65. "label" => sanitize_state_name($_x),
  66. "type" => DB::getSchemaBuilder()->getColumnType($tables[$i], $_x)
  67. ];
  68. }, $matches);
  69. $columns = array_merge($columns, $matches);
  70. }
  71. $columns = array_values($columns);
  72. $columns = json_decode(json_encode($columns));
  73. // dd($matches);
  74. return json_encode([
  75. "success" => true,
  76. "data" => $columns
  77. ]);
  78. }
  79. public function viewData(Request $request, StatTreeLine $line) {
  80. $total = 0;
  81. $rows = [];
  82. $columns = [];
  83. $selectColumns = [];
  84. $paginator = null;
  85. foreach ($line->reportColumns as $reportColumn) {
  86. $columns[] = [
  87. "label" => $reportColumn->label,
  88. "column" => $reportColumn->display_key,
  89. "type" => $reportColumn->field_type,
  90. "as" => "v_{$reportColumn->id}"
  91. ];
  92. $selectColumns[] = "{$reportColumn->display_key} as v_{$reportColumn->id}";
  93. }
  94. if(count($line->reportColumns)) {
  95. $result = $this->queryStatTreeLineData($line, $selectColumns, $columns, $request);
  96. $total = $result[0];
  97. $rows = $result[1];
  98. $paginator = new LengthAwarePaginator($rows, $total, $request->input('per_page') ?: 20, $request->input('page') ?: 1);
  99. $paginator->setPath(route('practice-management.statTreeLines.view-data', compact('line')));
  100. }
  101. return view('app.stat-tree.stat-tree-lines.view-data', compact('line', 'total', 'rows', 'columns', 'paginator'));
  102. }
  103. public function refreshCountQuery(Request $request)
  104. {
  105. $statTreeLineID = $request->get('statTreeLineID');
  106. if (!$statTreeLineID) return $this->fail('No specified stat tree line!');
  107. $statTreeLine = StatTreeLine::where('id', $statTreeLineID)->first();
  108. if (!$statTreeLine) return $this->fail('Invalid stat tree line!');
  109. $query = $this->applyStatTreeLineQueryClauses($statTreeLine);
  110. if ($query) {
  111. $statTreeLine->last_refresh_count = $query[0]->count;
  112. $statTreeLine->save();
  113. return $this->pass($statTreeLine->last_refresh_count);
  114. }else{
  115. return $this->fail('Invalid query or model/table name');
  116. }
  117. }
  118. protected function applyStatTreeLineQueryClauses(StatTreeLine $statTreeLine)
  119. {
  120. $model = $statTreeLine->statTree->model;
  121. // $query = null;
  122. // if (strcasecmp($model, 'client') == 0) {
  123. // $query = Client::query();
  124. // }
  125. // if (strcasecmp($model, 'pro') == 0) {
  126. // $query = Pro::query();
  127. // }
  128. // if (!$query) return null;
  129. $clauses = [];
  130. foreach ($statTreeLine->lineClauses as $lineClause) {
  131. $clauseText = $lineClause->clause->clause_text;
  132. $isValid = $this->cleanupClause($clauseText);
  133. if ($isValid) {
  134. array_push($clauses, $clauseText);
  135. }
  136. }
  137. $query = 'SELECT COUNT(*) FROM '.$model.' WHERE '. implode(" AND ", $clauses);
  138. return DB::select($query);
  139. }
  140. protected function queryStatTreeLineData(StatTreeLine $statTreeLine, $selectColumns, $columns, Request $request)
  141. {
  142. $model = $statTreeLine->statTree->model;
  143. $clauses = [];
  144. foreach ($statTreeLine->lineClauses as $lineClause) {
  145. $clauseText = $lineClause->clause->clause_text;
  146. // apply arg values
  147. foreach ($lineClause->clause->clauseArgs as $clauseArg) {
  148. $value = null;
  149. foreach ($lineClause->lineClauseArgs as $lineClauseArg) {
  150. if($lineClauseArg->clause_arg_id === $clauseArg->id) {
  151. $value = $lineClauseArg->value;
  152. }
  153. }
  154. if(!is_null($value)) {
  155. $clauseText = str_replace(
  156. ':' . $clauseArg->arg_text, // search for :xxx
  157. "'" . $value . "'::" . $clauseArg->field_type, // replace with '$value'::$field_type
  158. $clauseText);
  159. }
  160. }
  161. array_push($clauses, $clauseText);
  162. }
  163. // if stat tree bound to a pro, apply pro_scope_clause
  164. if($request->input('proUid') && $statTreeLine->statTree->pro_scope_clause) {
  165. $pro = Pro::where('uid', $request->input('proUid'))->first();
  166. if($pro) {
  167. $clauses[] = str_replace('@PRO_ID', $pro->id, $statTreeLine->statTree->pro_scope_clause);
  168. }
  169. }
  170. elseif($statTreeLine->statTree->pro && $statTreeLine->statTree->pro_scope_clause) {
  171. $clauses[] = str_replace('@PRO_ID', $statTreeLine->statTree->pro->id, $statTreeLine->statTree->pro_scope_clause);
  172. }
  173. // filters from view-data UI
  174. foreach ($columns as $column) {
  175. if($request->input($column['as'] . '_op')) {
  176. switch($column['type']) {
  177. case 'integer':
  178. case 'bigint':
  179. case 'decimal':
  180. case 'bool':
  181. case 'boolean':
  182. if($request->input($column['as'] . '_value')) {
  183. $clauses[] = "{$column['column']} " . $request->input($column['as'] . '_op') . ' ' . $request->input($column['as'] . '_value');
  184. }
  185. break;
  186. case 'string':
  187. case 'text':
  188. case 'varchar':
  189. if($request->input($column['as'] . '_value')) {
  190. if($request->input($column['as'] . '_op') === '=' || $request->input($column['as'] . '_op') === '!=') {
  191. $clauses[] = "{$column['column']} " . $request->input($column['as'] . '_op') . ' ' . $request->input($column['as'] . '_value');
  192. }
  193. elseif($request->input($column['as'] . '_op') === 'ILIKE' || $request->input($column['as'] . '_op') === 'NOT ILIKE') {
  194. $clauses[] = "{$column['column']} " . $request->input($column['as'] . '_op') . ' ' . "'%" . $request->input($column['as'] . '_value') . "%'";
  195. }
  196. }
  197. break;
  198. case 'date':
  199. case 'datetime':
  200. if($request->input($column['as'] . '_value_start')) {
  201. $clauses[] = "{$column['column']} >= " . "'" . $request->input($column['as'] . '_value_start') . "'";
  202. }
  203. if($request->input($column['as'] . '_value_end')) {
  204. $clauses[] = "{$column['column']} <= " . "'" . $request->input($column['as'] . '_value_end') . "'";
  205. }
  206. break;
  207. }
  208. }
  209. }
  210. $result = null;
  211. // get count for paginator
  212. try {
  213. $query = 'SELECT COUNT(*) FROM '.$model.' WHERE '. implode(" AND ", $clauses);
  214. $result = DB::select($query);
  215. $total = $result[0]->count;
  216. $page = $request->input('page') ?: 1;
  217. $perPage = $request->input('per_page') ?: 20;
  218. $offset = ($page - 1) * $perPage;
  219. $sort = '';
  220. if($request->input('sort_by') && $request->input('sort_by')) {
  221. $sort = "ORDER BY " . $request->input('sort_by') . " " . $request->input('sort_dir') . " NULLS LAST";
  222. }
  223. $query = 'SELECT ' . implode(", ", $selectColumns) . ' FROM '.$model.' WHERE '. implode(" AND ", $clauses) . " {$sort} OFFSET {$offset} LIMIT {$perPage}";
  224. $result = DB::select($query);
  225. $result = [$total, $result];
  226. }
  227. catch (\Exception $ex) {
  228. $result = 'error';
  229. }
  230. return $result;
  231. }
  232. protected function cleanupClause($clauseText)
  233. {
  234. //Dont include empty clauses, i.e ()
  235. preg_match('#\((.*?)\)#', $clauseText, $match);
  236. $content = @$match[1];
  237. if (!$content || empty($content)) return null;
  238. return $content;
  239. }
  240. // eps
  241. public function create(Request $request) {
  242. // stat tree line
  243. $statTreeLine = new StatTreeLine();
  244. $nextId = DB::select("select nextval('stat_tree_line_id_seq')");
  245. $statTreeLine->id = $nextId[0]->nextval;
  246. $statTreeLine->uid = Uuid::uuid4();
  247. $statTreeLine->stat_tree_id = $request->input('statTreeId');
  248. $statTreeLine->parent_stat_tree_line_id = $request->input('parentStatTreeLineId');
  249. $positionIndex = DB::select("select max(tree_order_position_index) from stat_tree_line where stat_tree_id = {$statTreeLine->stat_tree_id}" .
  250. ($statTreeLine->parent_stat_tree_line_id ? " AND parent_stat_tree_line_id = {$statTreeLine->parent_stat_tree_line_id}" : '')
  251. );
  252. $statTreeLine->tree_order_position_index = is_numeric($positionIndex[0]->max) ? $positionIndex[0]->max + 1 : 1;
  253. $statTreeLine->save();
  254. // stat tree line clauses (all of parent's clauses + own)
  255. if($statTreeLine->parent) {
  256. foreach($statTreeLine->parent->lineClauses as $lineClause) {
  257. $statTreeLineClause = new StatTreeLineClause();
  258. $nextId = DB::select("select nextval('stat_tree_line_clause_id_seq')");
  259. $statTreeLineClause->id = $nextId[0]->nextval;
  260. $statTreeLineClause->uid = Uuid::uuid4();
  261. $statTreeLineClause->stat_tree_line_id = $statTreeLine->id;
  262. $statTreeLineClause->clause_id = $lineClause->clause_id;
  263. $statTreeLineClause->clause_label = $lineClause->clause_label;
  264. $statTreeLineClause->save();
  265. }
  266. }
  267. $statTreeLineClause = new StatTreeLineClause();
  268. $nextId = DB::select("select nextval('stat_tree_line_clause_id_seq')");
  269. $statTreeLineClause->id = $nextId[0]->nextval;
  270. $statTreeLineClause->uid = Uuid::uuid4();
  271. $statTreeLineClause->stat_tree_line_id = $statTreeLine->id;
  272. $clause = Clause::where('id', $request->input('clauseId'))->first();
  273. $statTreeLineClause->clause_id = $clause->id;
  274. $statTreeLineClause->clause_label = $clause->label;
  275. $statTreeLineClause->save();
  276. return $this->pass();
  277. }
  278. public function remove(Request $request) {
  279. $statTreeLine = StatTreeLine::where('uid', $request->input('uid'))->first();
  280. if(!$statTreeLine) return $this->fail('Stat tree line not found!');
  281. // TODO: disallow if this has children
  282. DB::select("delete from stat_tree_line where id = {$statTreeLine->id}");
  283. return $this->pass();
  284. }
  285. }