ImportService.php 91 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363
  1. <?php
  2. namespace App\Service;
  3. use App\Exports\TableHeadExport;
  4. use App\Import\ImportAll;
  5. use App\Model\Depart;
  6. use App\Model\Device;
  7. use App\Model\Employee;
  8. use App\Model\EmployeeDepartPermission;
  9. use App\Model\Fee;
  10. use App\Model\Item;
  11. use App\Model\ItemDetails;
  12. use App\Model\MonthlyDdOrder;
  13. use App\Model\MonthlyPsOrder;
  14. use App\Model\MonthlyPwOrder;
  15. use Illuminate\Support\Facades\DB;
  16. use Maatwebsite\Excel\Facades\Excel;
  17. use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\F;
  18. use PhpOffice\PhpSpreadsheet\IOFactory;
  19. use PhpOffice\PhpSpreadsheet\Shared\Date;
  20. class ImportService extends Service
  21. {
  22. public static $type = [
  23. 'depart', // 部门
  24. 'employee', // 用户
  25. 'device', // 设备
  26. 'item', // 项目
  27. 'fee', // 费用
  28. 'monthPwOrder', // 人员月度研发工时单
  29. 'monthDwOrder', // 设备月度研发工时单
  30. 'monthPsOrder', // 人员月度工资单
  31. 'monthDdOrder', // 设备月度折旧单
  32. ];
  33. public function getTableTitleXls($data,$user){
  34. if(empty($data['type'])) return [false,'缺少类型'];
  35. //获取配置文件
  36. $fuc = $data['type'];
  37. if (! method_exists(self::class, $fuc)) return [false, "导入文件方法获取不存在,请联系开发"];
  38. list($status,$return) = $this->$fuc($data,$user);
  39. list($msg,$filename) = $return;
  40. if(! $status) return [false, $msg];
  41. $headers = array_column($msg,'value');
  42. $comments = $enums = [];
  43. foreach ($msg as $value){
  44. if(! empty($value['comments'])) $comments[$value['value']] = $value['comments'];
  45. if(! empty($value['enums'])) $enums[$value['value']] = $value['enums'];
  46. }
  47. Excel::store(new TableHeadExport([], $headers, $comments, $enums),"/public/export/{$filename}", null, 'Xlsx', []);
  48. return [true, ['file' => $filename]];
  49. }
  50. private function getTableConfig($type = ""){
  51. if(empty($type)) return [];
  52. //获取配置文件
  53. $config = "excel." . $type;
  54. return config($config) ?? [];
  55. }
  56. private function depart($data,$user){
  57. $config = $this->getTableConfig($data['type']);
  58. if(empty($config)) return [false, ['导入配置表头文件不存在','']];
  59. $config_array = $config['array'] ?? [];
  60. //生成下载文件
  61. $filename = $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
  62. return [true, [$config_array, $filename]];
  63. }
  64. private function employee($data,$user){
  65. $config = $this->getTableConfig($data['type']);
  66. if(empty($config)) return [false, ['导入配置表头文件不存在','']];
  67. $config_array = $config['array'] ?? [];
  68. //生成下载文件
  69. $filename = $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
  70. return [true, [$config_array, $filename]];
  71. }
  72. private function device($data,$user){
  73. $config = $this->getTableConfig($data['type']);
  74. if(empty($config)) return [false, ['导入配置表头文件不存在','']];
  75. $config_array = $config['array'] ?? [];
  76. //生成下载文件
  77. $filename = $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
  78. return [true, [$config_array, $filename]];
  79. }
  80. private function item($data,$user){
  81. $config = $this->getTableConfig($data['type']);
  82. if(empty($config)) return [false, ['导入配置表头文件不存在','']];
  83. $config_array = $config['array'] ?? [];
  84. //生成下载文件
  85. $filename = $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
  86. return [true, [$config_array, $filename]];
  87. }
  88. private function fee($data,$user){
  89. $config = $this->getTableConfig($data['type']);
  90. if(empty($config)) return [false, ['导入配置表头文件不存在','']];
  91. $config_array = $config['array'] ?? [];
  92. //生成下载文件
  93. $filename = $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
  94. return [true, [$config_array, $filename]];
  95. }
  96. private function monthPwOrder($data,$user){
  97. $config = $this->getTableConfig($data['type']);
  98. if(empty($config)) return [false, ['导入配置表头文件不存在','']];
  99. $config_array = $config['array'] ?? [];
  100. //生成下载文件
  101. $filename = $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
  102. return [true, [$config_array, $filename]];
  103. }
  104. private function monthDwOrder($data,$user){
  105. $config = $this->getTableConfig($data['type']);
  106. if(empty($config)) return [false, ['导入配置表头文件不存在','']];
  107. $config_array = $config['array'] ?? [];
  108. //生成下载文件
  109. $filename = $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
  110. return [true, [$config_array, $filename]];
  111. }
  112. private function monthPsOrder($data,$user){
  113. $config = $this->getTableConfig($data['type']);
  114. if(empty($config)) return [false, ['导入配置表头文件不存在','']];
  115. $config_array = $config['array'] ?? [];
  116. //生成下载文件
  117. $filename = $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
  118. return [true, [$config_array, $filename]];
  119. }
  120. private function monthDdOrder($data,$user){
  121. $config = $this->getTableConfig($data['type']);
  122. if(empty($config)) return [false, ['导入配置表头文件不存在','']];
  123. $config_array = $config['array'] ?? [];
  124. //生成下载文件
  125. $filename = $config['name'] . "导入模板_" . time() . '.' . 'xlsx';
  126. return [true, [$config_array, $filename]];
  127. }
  128. //导入入口
  129. public function importAll($data,$user){
  130. // //不超时
  131. // ini_set('max_execution_time', 0);
  132. // //内存设置
  133. // ini_set('memory_limit', -1);
  134. // $reader = IOFactory::createReader('Xlsx');
  135. // $reader->setReadDataOnly(true); // 只读取有数据的单元格
  136. // $spreadsheet = $reader->load($data['file']);
  137. // dd($spreadsheet);
  138. // // 创建一个Reader对象
  139. // $reader = IOFactory::createReader('Xlsx'); // 根据你的文件格式选择合适的reader
  140. //
  141. //// 加载Excel文件
  142. // $spreadsheet = $reader->load($data['file']);
  143. //
  144. //// 获取第一个工作表
  145. // $worksheet = $spreadsheet->getActiveSheet();
  146. //
  147. //// 获取总行数
  148. // $totalRows = $worksheet->getHighestRow();dd($totalRows);
  149. if(empty($data['type'])) return [false,'缺少导入类型,导入失败'];
  150. if(! in_array($data['type'],self::$type)) return [false,'导入类型不存在,导入失败'];
  151. if(empty($data['file'])) return [false,'导入文件不能为空'];
  152. try {
  153. $import = new ImportAll();
  154. //设置导入人id
  155. $import->setCrt($user['id']);
  156. $import->setUser($user);
  157. $import->setType($data['type']);
  158. $other = $data;
  159. unset($other['file']);
  160. $import->setOtherParam($other);
  161. //导入
  162. \Maatwebsite\Excel\Facades\Excel::import($import,$data['file']);
  163. if($import->getMsg()) {
  164. $bool = $import->getIsLongText();
  165. if($bool) {
  166. return [0, $import->getMsg()];
  167. }else{
  168. return [false, $import->getMsg()];
  169. }
  170. }
  171. }catch (\Throwable $exception) {
  172. return [false, $exception->getMessage() . ' (Code: ' . $exception->getCode() . ', Line: ' . $exception->getLine() . ')'];
  173. }
  174. return [true, ''];
  175. }
  176. // 项目 -----------------------------------
  177. public function employeeImport($array, $user, $other_param)
  178. {
  179. $upload = $array[0];
  180. list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
  181. if (!$status) return [false, $msg];
  182. $table_config = $msg;
  183. unset($array[0]);
  184. if (empty($array)) return [false, '导入数据不能为空'];
  185. // 1. 公共校验 (必填、唯一性等)
  186. list($array, $error) = $this->checkCommon($array, $table_config);
  187. if (!empty($error)) return [0, $error];
  188. // 2. 业务详细校验 (获取更新映射及明细数据)
  189. list($error, $update_map, $detail_data_map) = $this->employeeCheck($array, $user, $table_config);
  190. if (!empty($error)) return [0, $error];
  191. $time = time();
  192. $insert_data = [];
  193. $update_data = [];
  194. $all_detail_insert = [];
  195. $update_main_ids = [];
  196. // 3. 数据分拣
  197. foreach ($array as $key => $value) {
  198. $main_tmp = [];
  199. foreach ($value as $k => $val) {
  200. if (!empty($table_config[$k]['is_main'])) {
  201. $main_tmp[$table_config[$k]['key']] = $val;
  202. }
  203. }
  204. if (isset($update_map[$key])) {
  205. // 更新逻辑
  206. $empId = $update_map[$key];
  207. $update_data[] = array_merge($main_tmp, ['id' => $empId]);
  208. $update_main_ids[] = $empId;
  209. // 收集明细 (后续统一插入)
  210. if (isset($detail_data_map[$key])) {
  211. foreach ($detail_data_map[$key] as $d) {
  212. $all_detail_insert[] = array_merge($d, ['employee_id' => $empId, 'top_depart_id' => $user['top_depart_id']]);
  213. }
  214. }
  215. } else {
  216. // 新增逻辑
  217. $main_tmp['account'] = $user['top_depart_code'] . "_" . $main_tmp['number'];
  218. $main_tmp['top_depart_id'] = $user['top_depart_id'];
  219. $main_tmp['crt_id'] = $user['id'];
  220. $main_tmp['crt_time'] = $time;
  221. // 以 number 为键,方便后续回填 ID
  222. $insert_data[$main_tmp['number']] = $main_tmp;
  223. if (isset($detail_data_map[$key])) {
  224. foreach ($detail_data_map[$key] as $d) {
  225. $all_detail_insert[] = array_merge($d, ['_number' => $main_tmp['number'], 'top_depart_id' => $main_tmp['top_depart_id']]);
  226. }
  227. }
  228. }
  229. }
  230. DB::beginTransaction();
  231. try {
  232. // 4. 执行新增主表
  233. if (!empty($insert_data)) {
  234. foreach (array_chunk($insert_data, 500) as $chunk) {
  235. Employee::insert($chunk);
  236. }
  237. // 获取新插入数据的 ID 映射
  238. $new_item_maps = Employee::whereIn('number', array_keys($insert_data))
  239. ->where('del_time', 0)
  240. ->where('top_depart_id', $user['top_depart_id'])
  241. ->pluck('id', 'number')->toArray();
  242. }
  243. // 5. 执行更新主表 (分批更新)
  244. if (!empty($update_data)) {
  245. foreach (array_chunk($update_data, 100) as $chunk) {
  246. foreach ($chunk as $uItem) {
  247. $id = $uItem['id'];
  248. unset($uItem['id']);
  249. Employee::where('id', $id)->update($uItem);
  250. }
  251. }
  252. }
  253. // 6. 处理明细表 (先删后插策略)
  254. // 删除旧明细 (逻辑删除)
  255. if (!empty($update_main_ids)) EmployeeDepartPermission::whereIn('employee_id', $update_main_ids)->delete();
  256. // 回填新增主表的 ID 到明细数组
  257. foreach ($all_detail_insert as &$di) {
  258. if (isset($di['_number'])) {
  259. $di['employee_id'] = $new_item_maps[$di['_number']] ?? 0;
  260. unset($di['_number']);
  261. }
  262. }
  263. unset($di);
  264. // 批量插入所有明细
  265. if (!empty($all_detail_insert)) {
  266. foreach (array_chunk($all_detail_insert, 500) as $chunk) {
  267. EmployeeDepartPermission::insert($chunk);
  268. }
  269. }
  270. DB::commit();
  271. } catch (\Exception $e) {
  272. DB::rollBack();
  273. return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
  274. }
  275. return [true, ''];
  276. }
  277. private function getEmployeeList($array, $user, $index){
  278. //查找设备
  279. $codes = array_unique(array_filter(array_column($array,$index)));
  280. return Employee::where('del_time', 0)
  281. ->where('top_depart_id', $user['top_depart_id'])
  282. ->whereIn('number', $codes)
  283. ->pluck('id','number')
  284. ->toArray();
  285. }
  286. private function employeeCheck(&$array, $user, $table_config)
  287. {
  288. $keys = array_column($table_config, 'key');
  289. $codeIdx = array_search('number', $keys);
  290. $sexIdx = array_search('sex', $keys);
  291. $eductionIdx = array_search('education', $keys);
  292. $stateIdx = array_search('state', $keys);
  293. $depIdx = array_search('depart_list', $keys);
  294. $code_map = $this->getEmployeeList($array, $user, $codeIdx);
  295. $dep_map = $this->getEDataList($array, $user, $depIdx);
  296. $errors = [];
  297. $update_mapping = [];
  298. $detail_storage = [];
  299. $sex_map = array_flip(Employee::SEX_TYPE);
  300. $e_map = array_flip(Employee::Education);
  301. $state_map = array_flip(Employee::State_Type);
  302. foreach ($array as $rowIndex => $rowValue) {
  303. $displayLine = $rowIndex + 1;
  304. $valCode = $rowValue[$codeIdx] ?? '';
  305. // 1. 判定更新还是新增
  306. if (isset($code_map[$valCode])) {
  307. $update_mapping[$rowIndex] = $code_map[$valCode];
  308. }
  309. // 2. 校验
  310. $sex_text = $rowValue[$sexIdx] ?? '';
  311. if(! empty($sex_text)){
  312. if (!isset($sex_map[$sex_text])) {
  313. $errors[] = "第{$displayLine}行:性别[{$sex_text}]无效";
  314. } else {
  315. $array[$rowIndex][$sexIdx] = $sex_map[$sex_text];
  316. }
  317. }
  318. $e_text = $rowValue[$eductionIdx] ?? '';
  319. if(! empty($e_text)){
  320. if (!isset($e_map[$e_text])) {
  321. $errors[] = "第{$displayLine}行:性别[{$e_text}]无效";
  322. } else {
  323. $array[$rowIndex][$eductionIdx] = $e_map[$e_text];
  324. }
  325. }
  326. $state_text = $rowValue[$stateIdx] ?? '';
  327. if (!isset($state_map[$state_text])) {
  328. $errors[] = "第{$displayLine}行:状态[{$state_text}]无效";
  329. } else {
  330. $array[$rowIndex][$stateIdx] = $state_map[$state_text];
  331. }
  332. // 4. 解析部门 (明细类型1)
  333. if ($depIdx !== false && !empty($rowValue[$depIdx])) {
  334. foreach (explode(',', $rowValue[$depIdx]) as $mNum) {
  335. $mNum = trim($mNum);
  336. if (!isset($dep_map[$mNum])) {
  337. $errors[] = "第{$displayLine}行:部门编码[{$mNum}]不存在";
  338. } else {
  339. $detail_storage[$rowIndex][] = [
  340. 'depart_id' => $dep_map[$mNum],
  341. ];
  342. }
  343. }
  344. }
  345. }
  346. $error_str = !empty($errors) ? implode('|', $errors) : "";
  347. return [$error_str, $update_mapping, $detail_storage];
  348. }
  349. private function getEDataList($array, $user, $index1)
  350. {
  351. $depNums = [];
  352. // 去重收集
  353. foreach ($array as $row) {
  354. if (!empty($row[$index1])) {
  355. foreach (explode(',', $row[$index1]) as $v) $depNums[trim($v)] = true;
  356. }
  357. }
  358. return Depart::where('del_time', 0)
  359. ->where('top_depart_id', $user['top_depart_id'])
  360. ->whereIn('code', array_keys($depNums))
  361. ->pluck('id', 'code')->toArray();
  362. }
  363. // 部门 ----------------------------------
  364. public function departImport($array, $user, $other_param)
  365. {
  366. $upload = $array[0];
  367. list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
  368. if (!$status) return [false, $msg];
  369. $table_config = $msg;
  370. unset($array[0]);
  371. if (empty($array)) return [false, '导入数据不能为空'];
  372. list($array, $error) = $this->checkCommon($array, $table_config);
  373. if (!empty($error)) return [0, $error];
  374. // 2. 详细校验
  375. list($error, $update_map, $parent_code_map) = $this->departCheck($array, $user, $table_config);
  376. if (!empty($error)) return [0, $error];
  377. $time = time();
  378. $insert = [];
  379. $update = [];
  380. $all_codes = [];
  381. $all_parent_codes = [];
  382. // --- 修正点 1: 必须确保索引提取准确 ---
  383. $keys = array_column($table_config, 'key');
  384. $codeIdx = array_search('code', $keys);
  385. // ------------------------------------
  386. foreach ($array as $key => $value) {
  387. $cCode = trim($value[$codeIdx] ?? '');
  388. if($cCode === '') continue;
  389. $all_codes[] = $cCode;
  390. if (isset($parent_code_map[$key])) {
  391. $all_parent_codes[] = $parent_code_map[$key];
  392. }
  393. $main_tmp = [];
  394. foreach ($value as $k => $val){
  395. if(!empty($table_config[$k]['is_main'])){
  396. if ($table_config[$k]['key'] !== 'parent_id') {
  397. $main_tmp[$table_config[$k]['key']] = $val;
  398. }
  399. }
  400. }
  401. if (isset($update_map[$key])) {
  402. $update[] = array_merge($main_tmp, ['id' => $update_map[$key]]);
  403. } else {
  404. $main_tmp['top_depart_id'] = $user['top_depart_id'];
  405. $main_tmp['crt_time'] = $time;
  406. $main_tmp['parent_id'] = 0;
  407. $insert[] = $main_tmp;
  408. }
  409. }
  410. $searchCodes = array_unique(array_merge($all_codes, $all_parent_codes));
  411. DB::beginTransaction();
  412. try {
  413. if (!empty($insert)) {
  414. foreach (array_chunk($insert, 500) as $chunk) {
  415. Depart::insert($chunk);
  416. }
  417. }
  418. if (!empty($update)) {
  419. foreach ($update as $item) {
  420. $uId = $item['id']; unset($item['id']);
  421. Depart::where('id', $uId)->update($item);
  422. }
  423. }
  424. // --- 修正点 2: 核心回填逻辑 ---
  425. $newCodeToIdMap = Depart::where('del_time', 0)
  426. ->where('top_depart_id', $user['top_depart_id'])
  427. ->whereIn('code', $searchCodes)
  428. ->pluck('id', 'code')
  429. ->toArray();
  430. foreach ($parent_code_map as $rowIndex => $pCode) {
  431. // 这里必须通过 $rowIndex 从原始 $array 中精准获取当前行的编码
  432. $currentCode = isset($array[$rowIndex][$codeIdx]) ? trim($array[$rowIndex][$codeIdx]) : '';
  433. $pCode = trim($pCode);
  434. if ($currentCode === '' || $pCode === '') continue;
  435. $currentId = $newCodeToIdMap[$currentCode] ?? null;
  436. $parentId = $newCodeToIdMap[$pCode] ?? null;
  437. // 严谨判断:只有当前记录存在,且上级记录也存在,且两者不是同一个 ID 时才更新
  438. if ($currentId && $parentId && $currentId != $parentId) {
  439. Depart::where('id', $currentId)->update(['parent_id' => $parentId]);
  440. }
  441. }
  442. DB::commit();
  443. } catch (\Exception $e) {
  444. DB::rollBack();
  445. return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
  446. }
  447. return [true, ''];
  448. }
  449. private function departCheck(&$array, $user, $table_config)
  450. {
  451. $keys = array_column($table_config, 'key');
  452. $codeIdx = array_search('code', $keys);
  453. $parentIdx = array_search('parent_id', $keys);
  454. // 1. 获取基础数据
  455. list($dbFeeMap, $excelCodesMap) = $this->getDepartList($array, $user, $codeIdx);
  456. $errors = [];
  457. $update = [];
  458. $parent_code_map = [];
  459. // 2. 建立 Excel 内部父子关系映射(用于环路追溯)
  460. $currentExcelMap = [];
  461. foreach ($array as $row) {
  462. $c = trim($row[$codeIdx] ?? '');
  463. $p = trim($row[$parentIdx] ?? '');
  464. if ($c !== '') $currentExcelMap[$c] = $p ?: $user['top_depart_code'] ;
  465. }
  466. // 3. 逐行校验
  467. foreach ($array as $rowIndex => $value) {
  468. $displayLine = $rowIndex + 1;
  469. $valCode = trim($value[$codeIdx] ?? '');
  470. $valParentCode = trim($value[$parentIdx] ?? '');
  471. if ($valCode === '') continue;
  472. // 更新状态记录
  473. if (isset($dbFeeMap[$valCode])) {
  474. $update[$rowIndex] = $dbFeeMap[$valCode]['id'];
  475. }
  476. if ($valParentCode !== '') {
  477. // --- A. 存在性校验 ---
  478. // 这里会检查 006 是否在数据库,或者是否在本次 Excel 的其他行中
  479. if (!isset($dbFeeMap[$valParentCode]) && !isset($excelCodesMap[$valParentCode])) {
  480. $errors[] = "第{$displayLine}行:上级编码[{$valParentCode}]在系统和文件中均不存在";
  481. continue;
  482. }
  483. // --- B. 自引用校验 ---
  484. if ($valCode === $valParentCode) {
  485. $errors[] = "第{$displayLine}行:上级编码不能是自身";
  486. continue;
  487. }
  488. // --- C. 环路追溯 ---
  489. $visited = [];
  490. $res = $this->findDLoopInAncestors($valParentCode, $valCode, $currentExcelMap, $dbFeeMap, $visited);
  491. if ($res !== false) {
  492. if ($res['type'] === 'LOOP_SELF') {
  493. $errors[] = "第{$displayLine}行:编码[{$valCode}]与上级[{$valParentCode}]存在循环引用";
  494. } else {
  495. $errors[] = "第{$displayLine}行:上级[{$valParentCode}]的溯源链条已成环";
  496. }
  497. continue;
  498. }
  499. // 记录有效的父级关系
  500. $parent_code_map[$rowIndex] = $valParentCode;
  501. }else{
  502. $parent_code_map[$rowIndex] = $user['top_depart_code'];
  503. }
  504. }
  505. $error_string = !empty($errors) ? implode('|', $errors) : "";
  506. return [$error_string, $update, $parent_code_map];
  507. }
  508. private function getDepartList($array, $user, $index)
  509. {
  510. // 关键:一定要把 Excel 里所有的 code 这一列全部拿出来,并去除空值
  511. $codesInExcel = [];
  512. foreach ($array as $row) {
  513. $c = trim($row[$index] ?? '');
  514. if ($c !== '') {
  515. $codesInExcel[$c] = true;
  516. }
  517. }
  518. $allFees = Depart::where('del_time', 0)
  519. ->where('top_depart_id', $user['top_depart_id'])
  520. ->select('id', 'code', 'parent_id')
  521. ->get();
  522. $dbFeeMap = [];
  523. foreach ($allFees as $fee) {
  524. $dbFeeMap[$fee->code] = [
  525. 'id' => $fee->id,
  526. 'code' => $fee->code,
  527. 'parent_id' => $fee->parent_id
  528. ];
  529. }
  530. // 返回数据库映射和 Excel 编码映射
  531. return [$dbFeeMap, $codesInExcel];
  532. }
  533. private function findDLoopInAncestors($startParentCode, $targetCode, $excelMap, $dbFeeMap, &$visited)
  534. {
  535. $current = $startParentCode;
  536. while ($current !== '' && $current !== 0) {
  537. if ($current === $targetCode) {
  538. return ['type' => 'LOOP_SELF', 'code' => $current];
  539. }
  540. if (isset($visited[$current])) {
  541. return ['type' => 'EXISTING_LOOP', 'code' => $current];
  542. }
  543. $visited[$current] = true;
  544. if (isset($excelMap[$current]) && $excelMap[$current] !== '') {
  545. $current = $excelMap[$current];
  546. } elseif (isset($dbFeeMap[$current])) {
  547. $parentId = $dbFeeMap[$current]['parent_id'];
  548. $parentCode = '';
  549. foreach ($dbFeeMap as $item) {
  550. if ($item['id'] == $parentId) {
  551. $parentCode = $item['code'];
  552. break;
  553. }
  554. }
  555. $current = $parentCode;
  556. } else {
  557. break;
  558. }
  559. }
  560. return false;
  561. }
  562. // 设备 ------------------------------------
  563. public function deviceImport($array, $user, $other_param){
  564. $upload = $array[0];
  565. list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
  566. if(!$status) return [false, $msg];
  567. $table_config = $msg;
  568. unset($array[0]);
  569. if(empty($array)) return [false, '导入数据不能为空'];
  570. // 公共校验
  571. list($array, $error) = $this->checkCommon($array, $table_config);
  572. if(!empty($error)) return [0, $error];
  573. // 详细校验 (这里 $array 传引用,内部会转换日期)
  574. list($error, $update_map) = $this->deviceCheck($array, $user, $table_config);
  575. if(!empty($error)) return [0, $error];
  576. $time = time();
  577. $insert = [];
  578. $update = [];
  579. foreach ($array as $key => $value){
  580. $main_tmp = [];
  581. foreach ($value as $k => $val){
  582. if(!empty($table_config[$k]['is_main'])){
  583. $main_tmp[$table_config[$k]['key']] = $val;
  584. }
  585. }
  586. if(isset($update_map[$key])){
  587. // 存入待更新数组
  588. $update[] = array_merge($main_tmp, ['id' => $update_map[$key]]);
  589. } else {
  590. $main_tmp['top_depart_id'] = $user['top_depart_id'];
  591. $main_tmp['crt_id'] = $user['id'];
  592. $main_tmp['crt_time'] = $time;
  593. $insert[] = $main_tmp;
  594. }
  595. }
  596. DB::beginTransaction();
  597. try {
  598. // 1. 批量新增
  599. if(!empty($insert)){
  600. foreach(array_chunk($insert, 500) as $chunkInsert){
  601. Device::insert($chunkInsert);
  602. }
  603. }
  604. // 批量更新
  605. foreach (array_chunk($update, 100) as $chunk) {
  606. foreach ($chunk as $item) {
  607. $id = $item['id'];
  608. unset($item['id']);
  609. Device::where('id', $id)->update($item);
  610. }
  611. }
  612. DB::commit();
  613. } catch (\Exception $e) {
  614. DB::rollBack();
  615. return [false, "错误:" . $e->getMessage() . " 行:" . $e->getLine()];
  616. }
  617. return [true, ''];
  618. }
  619. private function deviceCheck(&$array, $user, $table_config)
  620. {
  621. // 动态获取关键列的索引
  622. $codeIdx = array_search('code', array_column($table_config, 'key'));
  623. $dateIdx = array_search('in_time', array_column($table_config, 'key'));
  624. $typeIdx = array_search('type', array_column($table_config, 'key'));
  625. $type2Idx = array_search('is_use', array_column($table_config, 'key'));
  626. $code_map = $this->getDeviceList($array, $user, $codeIdx);
  627. $errors = [];
  628. $update = [];
  629. $map_type = array_flip(Device::$type);
  630. $map_type_2 = array_flip(Device::Use);
  631. foreach ($array as $rowIndex => $value) {
  632. $displayLine = $rowIndex + 1;
  633. $valCode = $value[$codeIdx] ?? '';
  634. // 记录更新 ID
  635. if(isset($code_map[$valCode])){
  636. $update[$rowIndex] = $code_map[$valCode];
  637. }
  638. if(empty($map_type[$value[$typeIdx]])){
  639. $errors[] = "第{$displayLine}行固定资产类型错误";
  640. }else{
  641. $array[$rowIndex][$typeIdx] = $map_type[$value[$typeIdx]];
  642. }
  643. if(empty($map_type_2[$value[$type2Idx]])){
  644. $errors[] = "第{$displayLine}行是否启用错误";
  645. }else{
  646. $array[$rowIndex][$type2Idx] = $map_type_2[$value[$type2Idx]];
  647. }
  648. // 日期处理
  649. if($dateIdx !== false && !empty($value[$dateIdx])){
  650. list($status, $msg) = $this->convertExcelCellToDate($value[$dateIdx]);
  651. if(!$status) {
  652. $errors[] = "第{$displayLine}行日期格式错误";
  653. } else {
  654. $array[$rowIndex][$dateIdx] = $msg;
  655. }
  656. }
  657. }
  658. $error_string = "";
  659. if(! empty($errors)) $error_string = implode('|', $errors);
  660. return [$error_string, $update];
  661. }
  662. private function getDeviceList($array, $user, $index){
  663. //查找设备
  664. $codes = array_unique(array_filter(array_column($array,$index)));
  665. return Device::where('del_time', 0)
  666. ->where('top_depart_id', $user['top_depart_id'])
  667. ->whereIn('code', $codes)
  668. ->pluck('id','code')
  669. ->toArray();
  670. }
  671. // 项目 -----------------------------------
  672. public function itemImport($array, $user, $other_param)
  673. {
  674. $upload = $array[0];
  675. list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
  676. if (!$status) return [false, $msg];
  677. $table_config = $msg;
  678. unset($array[0]);
  679. if (empty($array)) return [false, '导入数据不能为空'];
  680. // 1. 公共校验 (必填、唯一性等)
  681. list($array, $error) = $this->checkCommon($array, $table_config);
  682. if (!empty($error)) return [0, $error];
  683. // 2. 业务详细校验 (获取更新映射及明细数据)
  684. list($error, $update_map, $detail_data_map) = $this->itemCheck($array, $user, $table_config);
  685. if (!empty($error)) return [0, $error];
  686. $time = time();
  687. $insert_data = [];
  688. $update_data = [];
  689. $all_detail_insert = [];
  690. $update_main_ids = [];
  691. // 3. 数据分拣
  692. foreach ($array as $key => $value) {
  693. $main_tmp = [];
  694. foreach ($value as $k => $val) {
  695. if (!empty($table_config[$k]['is_main'])) {
  696. $main_tmp[$table_config[$k]['key']] = $val;
  697. }
  698. }
  699. if (isset($update_map[$key])) {
  700. // 更新逻辑
  701. $itemId = $update_map[$key];
  702. $update_data[] = array_merge($main_tmp, ['id' => $itemId]);
  703. $update_main_ids[] = $itemId;
  704. // 收集明细 (后续统一插入)
  705. if (isset($detail_data_map[$key])) {
  706. foreach ($detail_data_map[$key] as $d) {
  707. $all_detail_insert[] = array_merge($d, ['item_id' => $itemId, 'crt_time' => $time, 'top_depart_id' => $user['top_depart_id']]);
  708. }
  709. }
  710. } else {
  711. // 新增逻辑
  712. $main_tmp['top_depart_id'] = $user['top_depart_id'];
  713. $main_tmp['crt_id'] = $user['id'];
  714. $main_tmp['crt_time'] = $time;
  715. // 以 code 为键,方便后续回填 ID
  716. $insert_data[$main_tmp['code']] = $main_tmp;
  717. if (isset($detail_data_map[$key])) {
  718. foreach ($detail_data_map[$key] as $d) {
  719. $all_detail_insert[] = array_merge($d, ['_code' => $main_tmp['code'], 'crt_time' => $time, 'top_depart_id' => $main_tmp['top_depart_id']]);
  720. }
  721. }
  722. }
  723. }
  724. DB::beginTransaction();
  725. try {
  726. // 4. 执行新增主表
  727. if (!empty($insert_data)) {
  728. foreach (array_chunk($insert_data, 500) as $chunk) {
  729. Item::insert($chunk);
  730. }
  731. // 获取新插入数据的 ID 映射
  732. $new_item_maps = Item::whereIn('code', array_keys($insert_data))
  733. ->where('del_time', 0)
  734. ->where('top_depart_id', $user['top_depart_id'])
  735. ->pluck('id', 'code')->toArray();
  736. }
  737. // 5. 执行更新主表 (分批更新)
  738. if (!empty($update_data)) {
  739. foreach (array_chunk($update_data, 100) as $chunk) {
  740. foreach ($chunk as $uItem) {
  741. $id = $uItem['id'];
  742. unset($uItem['id']);
  743. Item::where('id', $id)->update($uItem);
  744. }
  745. }
  746. }
  747. // 6. 处理明细表 (先删后插策略)
  748. // 删除旧明细 (逻辑删除)
  749. if (!empty($update_main_ids)) {
  750. ItemDetails::whereIn('item_id', $update_main_ids)
  751. ->where('del_time', 0)
  752. ->update(['del_time' => $time]);
  753. }
  754. // 回填新增主表的 ID 到明细数组
  755. foreach ($all_detail_insert as &$di) {
  756. if (isset($di['_code'])) {
  757. $di['item_id'] = $new_item_maps[$di['_code']] ?? 0;
  758. unset($di['_code']);
  759. }
  760. }
  761. unset($di);
  762. // 批量插入所有明细
  763. if (!empty($all_detail_insert)) {
  764. foreach (array_chunk($all_detail_insert, 500) as $chunk) {
  765. ItemDetails::insert($chunk);
  766. }
  767. }
  768. DB::commit();
  769. } catch (\Exception $e) {
  770. DB::rollBack();
  771. return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
  772. }
  773. return [true, ''];
  774. }
  775. private function getItemList($array, $user, $index){
  776. //查找设备
  777. $codes = array_unique(array_filter(array_column($array,$index)));
  778. return Item::where('del_time', 0)
  779. ->where('top_depart_id', $user['top_depart_id'])
  780. ->whereIn('code', $codes)
  781. ->pluck('id','code')
  782. ->toArray();
  783. }
  784. private function itemCheck(&$array, $user, $table_config)
  785. {
  786. $keys = array_column($table_config, 'key');
  787. $codeIdx = array_search('code', $keys);
  788. $stateIdx = array_search('state', $keys);
  789. $manIdx = array_search('man_list', $keys);
  790. $deviceIdx = array_search('device_list', $keys);
  791. // 日期索引
  792. $dateIdx = array_search('start_time', $keys);
  793. $date2Idx = array_search('end_time', $keys);
  794. $code_map = $this->getItemList($array, $user, $codeIdx);
  795. list($man_map, $device_map) = $this->getDataList($array, $user, $manIdx, $deviceIdx);
  796. $errors = [];
  797. $update_mapping = [];
  798. $detail_storage = [];
  799. $state_type_map = array_flip(Item::State_Type);
  800. foreach ($array as $rowIndex => $rowValue) {
  801. $displayLine = $rowIndex + 1;
  802. $valCode = $rowValue[$codeIdx] ?? '';
  803. // 1. 判定更新还是新增
  804. if (isset($code_map[$valCode])) {
  805. $update_mapping[$rowIndex] = $code_map[$valCode];
  806. }
  807. // 2. 状态校验
  808. $state_text = $rowValue[$stateIdx] ?? '';
  809. if (!isset($state_type_map[$state_text])) {
  810. $errors[] = "第{$displayLine}行:状态[{$state_text}]无效";
  811. } else {
  812. $array[$rowIndex][$stateIdx] = $state_type_map[$state_text];
  813. }
  814. // 3. 日期转换
  815. foreach ([$dateIdx, $date2Idx] as $dIdx) {
  816. if ($dIdx !== false && !empty($rowValue[$dIdx])) {
  817. list($s, $m) = $this->convertExcelCellToDate($rowValue[$dIdx]);
  818. if (!$s) $errors[] = "第{$displayLine}行:日期格式非法";
  819. else $array[$rowIndex][$dIdx] = $m;
  820. }
  821. }
  822. // 4. 解析人员 (明细类型1)
  823. if ($manIdx !== false && !empty($rowValue[$manIdx])) {
  824. foreach (explode(',', $rowValue[$manIdx]) as $mNum) {
  825. $mNum = trim($mNum);
  826. if (!isset($man_map[$mNum])) {
  827. $errors[] = "第{$displayLine}行:人员工号[{$mNum}]不存在";
  828. } else {
  829. $detail_storage[$rowIndex][] = [
  830. 'type' => ItemDetails::type_one,
  831. 'data_id' => $man_map[$mNum],
  832. ];
  833. }
  834. }
  835. }
  836. // 5. 解析设备 (明细类型2)
  837. if ($deviceIdx !== false && !empty($rowValue[$deviceIdx])) {
  838. foreach (explode(',', $rowValue[$deviceIdx]) as $dCode) {
  839. $dCode = trim($dCode);
  840. if (!isset($device_map[$dCode])) {
  841. $errors[] = "第{$displayLine}行:设备的资产编码[{$dCode}]不存在";
  842. } else {
  843. $detail_storage[$rowIndex][] = [
  844. 'type' => ItemDetails::type_two,
  845. 'data_id' => $device_map[$dCode],
  846. ];
  847. }
  848. }
  849. }
  850. }
  851. $error_str = !empty($errors) ? implode('|', $errors) : "";
  852. return [$error_str, $update_mapping, $detail_storage];
  853. }
  854. private function getDataList($array, $user, $index1, $index2)
  855. {
  856. $manNums = [];
  857. $devCodes = [];
  858. // 去重收集
  859. foreach ($array as $row) {
  860. if (!empty($row[$index1])) {
  861. foreach (explode(',', $row[$index1]) as $v) $manNums[trim($v)] = true;
  862. }
  863. if (!empty($row[$index2])) {
  864. foreach (explode(',', $row[$index2]) as $v) $devCodes[trim($v)] = true;
  865. }
  866. }
  867. $manMap = Employee::where('del_time', 0)
  868. ->where('top_depart_id', $user['top_depart_id'])
  869. ->whereIn('number', array_keys($manNums))
  870. ->pluck('id', 'number')->toArray();
  871. $devMap = Device::where('del_time', 0)
  872. ->where('top_depart_id', $user['top_depart_id'])
  873. ->whereIn('code', array_keys($devCodes))
  874. ->pluck('id', 'code')->toArray();
  875. return [$manMap, $devMap];
  876. }
  877. // 费用 ----------------------------------
  878. public function feeImport($array, $user, $other_param)
  879. {
  880. $upload = $array[0];
  881. list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
  882. if (!$status) return [false, $msg];
  883. $table_config = $msg;
  884. unset($array[0]);
  885. if (empty($array)) return [false, '导入数据不能为空'];
  886. list($array, $error) = $this->checkCommon($array, $table_config);
  887. if (!empty($error)) return [0, $error];
  888. // 2. 详细校验
  889. list($error, $update_map, $parent_code_map) = $this->feeCheck($array, $user, $table_config);
  890. if (!empty($error)) return [0, $error];
  891. $time = time();
  892. $insert = [];
  893. $update = [];
  894. $all_codes = [];
  895. // --- 修正点 1: 必须确保索引提取准确 ---
  896. $keys = array_column($table_config, 'key');
  897. $codeIdx = array_search('code', $keys);
  898. // ------------------------------------
  899. foreach ($array as $key => $value) {
  900. $cCode = trim($value[$codeIdx] ?? '');
  901. if($cCode === '') continue;
  902. $all_codes[] = $cCode;
  903. $main_tmp = [];
  904. foreach ($value as $k => $val){
  905. if(!empty($table_config[$k]['is_main'])){
  906. if ($table_config[$k]['key'] !== 'parent_id') {
  907. $main_tmp[$table_config[$k]['key']] = $val;
  908. }
  909. }
  910. }
  911. if (isset($update_map[$key])) {
  912. $update[] = array_merge($main_tmp, ['id' => $update_map[$key]]);
  913. } else {
  914. $main_tmp['top_depart_id'] = $user['top_depart_id'];
  915. $main_tmp['crt_time'] = $time;
  916. $main_tmp['parent_id'] = 0;
  917. $insert[] = $main_tmp;
  918. }
  919. }
  920. $all_parent_codes = array_values($parent_code_map);
  921. $searchCodes = array_unique(array_merge($all_codes, $all_parent_codes));
  922. DB::beginTransaction();
  923. try {
  924. if (!empty($insert)) {
  925. foreach (array_chunk($insert, 500) as $chunk) {
  926. Fee::insert($chunk);
  927. }
  928. }
  929. if (!empty($update)) {
  930. foreach ($update as $item) {
  931. $uId = $item['id']; unset($item['id']);
  932. Fee::where('id', $uId)->update($item);
  933. }
  934. }
  935. // --- 修正点 2: 核心回填逻辑 ---
  936. $newCodeToIdMap = Fee::where('del_time', 0)
  937. ->where('top_depart_id', $user['top_depart_id'])
  938. ->whereIn('code', $searchCodes)
  939. ->pluck('id', 'code')
  940. ->toArray();
  941. foreach ($parent_code_map as $rowIndex => $pCode) {
  942. // 这里必须通过 $rowIndex 从原始 $array 中精准获取当前行的编码
  943. $currentCode = isset($array[$rowIndex][$codeIdx]) ? trim($array[$rowIndex][$codeIdx]) : '';
  944. $pCode = trim($pCode);
  945. if ($currentCode === '' || $pCode === '') continue;
  946. $currentId = $newCodeToIdMap[$currentCode] ?? null;
  947. $parentId = $newCodeToIdMap[$pCode] ?? null;
  948. // 严谨判断:只有当前记录存在,且上级记录也存在,且两者不是同一个 ID 时才更新
  949. if ($currentId && $parentId && $currentId != $parentId) {
  950. Fee::where('id', $currentId)->update(['parent_id' => $parentId]);
  951. }
  952. }
  953. DB::commit();
  954. } catch (\Exception $e) {
  955. DB::rollBack();
  956. return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
  957. }
  958. return [true, ''];
  959. }
  960. private function feeCheck(&$array, $user, $table_config)
  961. {
  962. $keys = array_column($table_config, 'key');
  963. $codeIdx = array_search('code', $keys);
  964. $parentIdx = array_search('parent_id', $keys);
  965. // 1. 获取基础数据
  966. list($dbFeeMap, $excelCodesMap) = $this->getFeeList($array, $user, $codeIdx);
  967. $errors = [];
  968. $update = [];
  969. $parent_code_map = [];
  970. // 2. 建立 Excel 内部父子关系映射(用于环路追溯)
  971. $currentExcelMap = [];
  972. foreach ($array as $row) {
  973. $c = trim($row[$codeIdx] ?? '');
  974. $p = trim($row[$parentIdx] ?? '');
  975. if ($c !== '') $currentExcelMap[$c] = $p;
  976. }
  977. // 3. 逐行校验
  978. foreach ($array as $rowIndex => $value) {
  979. $displayLine = $rowIndex + 1;
  980. $valCode = trim($value[$codeIdx] ?? '');
  981. $valParentCode = trim($value[$parentIdx] ?? '');
  982. if ($valCode === '') continue;
  983. // 更新状态记录
  984. if (isset($dbFeeMap[$valCode])) {
  985. $update[$rowIndex] = $dbFeeMap[$valCode]['id'];
  986. }
  987. if ($valParentCode !== '') {
  988. // --- A. 存在性校验 ---
  989. // 这里会检查 006 是否在数据库,或者是否在本次 Excel 的其他行中
  990. if (!isset($dbFeeMap[$valParentCode]) && !isset($excelCodesMap[$valParentCode])) {
  991. $errors[] = "第{$displayLine}行:上级编码[{$valParentCode}]在系统和文件中均不存在";
  992. continue;
  993. }
  994. // --- B. 自引用校验 ---
  995. if ($valCode === $valParentCode) {
  996. $errors[] = "第{$displayLine}行:上级编码不能是自身";
  997. continue;
  998. }
  999. // --- C. 环路追溯 ---
  1000. $visited = [];
  1001. $res = $this->findLoopInAncestors($valParentCode, $valCode, $currentExcelMap, $dbFeeMap, $visited);
  1002. if ($res !== false) {
  1003. if ($res['type'] === 'LOOP_SELF') {
  1004. $errors[] = "第{$displayLine}行:编码[{$valCode}]与上级[{$valParentCode}]存在循环引用";
  1005. } else {
  1006. $errors[] = "第{$displayLine}行:上级[{$valParentCode}]的溯源链条已成环";
  1007. }
  1008. continue;
  1009. }
  1010. // 记录有效的父级关系
  1011. $parent_code_map[$rowIndex] = $valParentCode;
  1012. }
  1013. }
  1014. $error_string = !empty($errors) ? implode('|', $errors) : "";
  1015. return [$error_string, $update, $parent_code_map];
  1016. }
  1017. private function getFeeList($array, $user, $index)
  1018. {
  1019. // 关键:一定要把 Excel 里所有的 code 这一列全部拿出来,并去除空值
  1020. $codesInExcel = [];
  1021. foreach ($array as $row) {
  1022. $c = trim($row[$index] ?? '');
  1023. if ($c !== '') {
  1024. $codesInExcel[$c] = true;
  1025. }
  1026. }
  1027. $allFees = Fee::where('del_time', 0)
  1028. ->where('top_depart_id', $user['top_depart_id'])
  1029. ->select('id', 'code', 'parent_id')
  1030. ->get();
  1031. $dbFeeMap = [];
  1032. foreach ($allFees as $fee) {
  1033. $dbFeeMap[$fee->code] = [
  1034. 'id' => $fee->id,
  1035. 'code' => $fee->code,
  1036. 'parent_id' => $fee->parent_id
  1037. ];
  1038. }
  1039. // 返回数据库映射和 Excel 编码映射
  1040. return [$dbFeeMap, $codesInExcel];
  1041. }
  1042. private function findLoopInAncestors($startParentCode, $targetCode, $excelMap, $dbFeeMap, &$visited)
  1043. {
  1044. $current = $startParentCode;
  1045. while ($current !== '' && $current !== 0) {
  1046. if ($current === $targetCode) {
  1047. return ['type' => 'LOOP_SELF', 'code' => $current];
  1048. }
  1049. if (isset($visited[$current])) {
  1050. return ['type' => 'EXISTING_LOOP', 'code' => $current];
  1051. }
  1052. $visited[$current] = true;
  1053. if (isset($excelMap[$current]) && $excelMap[$current] !== '') {
  1054. $current = $excelMap[$current];
  1055. } elseif (isset($dbFeeMap[$current])) {
  1056. $parentId = $dbFeeMap[$current]['parent_id'];
  1057. $parentCode = '';
  1058. foreach ($dbFeeMap as $item) {
  1059. if ($item['id'] == $parentId) {
  1060. $parentCode = $item['code'];
  1061. break;
  1062. }
  1063. }
  1064. $current = $parentCode;
  1065. } else {
  1066. break;
  1067. }
  1068. }
  1069. return false;
  1070. }
  1071. // 人员月度工时单 ------------------------------
  1072. public function monthPwOrderImport($array, $user, $other_param)
  1073. {
  1074. $upload = $array[0];
  1075. list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
  1076. if (!$status) return [false, $msg];
  1077. $table_config = $msg;
  1078. unset($array[0]);
  1079. if (empty($array)) return [false, '导入数据不能为空'];
  1080. list($array, $error) = $this->checkCommon($array, $table_config);
  1081. if (!empty($error)) return [0, $error];
  1082. // 2. 详细校验 (这里会返回 update_map 和 dbEmps 映射)
  1083. list($error, $update_map, $dbEmps) = $this->monthPwOrderCheck($array, $user, $table_config);
  1084. if (!empty($error)) return [0, $error];
  1085. $keys = array_column($table_config, 'key');
  1086. $codeIdx = array_search('code', $keys);
  1087. $monthIdx = array_search('month', $keys);
  1088. $empIdx = array_search('employee_id', $keys);
  1089. // --- 步骤 1: 数据聚合分组 ---
  1090. // 将 Excel 数据按“单据”维度归类
  1091. $groups = [];
  1092. foreach ($array as $rowIndex => $row) {
  1093. $cCode = trim($row[$codeIdx] ?? '');
  1094. $cMonthTs = $row[$monthIdx]; // 校验阶段已转为时间戳
  1095. // 聚合 Key:有编码用编码,没编码用月份标记
  1096. $aggKey = $cCode ?: "MONTH_" . $cMonthTs;
  1097. if (!isset($groups[$aggKey])) {
  1098. $groups[$aggKey] = [
  1099. 'main_id' => $update_map[$rowIndex] ?? 0, // 如果存在则是编辑
  1100. 'code' => $cCode,
  1101. 'month' => $cMonthTs,
  1102. 'details' => []
  1103. ];
  1104. }
  1105. // 准备详情行数据
  1106. $detailTmp = [];
  1107. foreach ($table_config as $k => $conf) {
  1108. if (!$conf['is_main']) {
  1109. $fieldKey = $conf['key'];
  1110. $fieldVal = $row[$k];
  1111. // 如果是人员,转换为 ID
  1112. if ($fieldKey == 'employee_id') {
  1113. $fieldVal = $dbEmps[$fieldVal] ?? 0;
  1114. }
  1115. $detailTmp[$fieldKey] = $fieldVal;
  1116. }
  1117. }
  1118. $groups[$aggKey]['details'][] = $detailTmp;
  1119. }
  1120. // --- 步骤 2: 开启事务写入 ---
  1121. DB::beginTransaction();
  1122. try {
  1123. $time = time();
  1124. foreach ($groups as $aggKey => $group) {
  1125. $mainId = $group['main_id'];
  1126. if ($mainId > 0) {
  1127. // 删除旧详情
  1128. DB::table('monthly_pw_order_details')->where('del_time',0)
  1129. ->where('main_id', $mainId)
  1130. ->update(['del_time' => $time]);
  1131. } else {
  1132. // B. 新增逻辑
  1133. $newCode = $this->generateBillNo([
  1134. 'top_depart_id' => $user['top_depart_id'],
  1135. 'type' => MonthlyPwOrder::Order_type,
  1136. 'period' => date("Ym", $group['month'])
  1137. ]);
  1138. $mainId = DB::table('monthly_pw_order')->insertGetId([
  1139. 'code' => $newCode,
  1140. 'month' => $group['month'],
  1141. 'top_depart_id' => $user['top_depart_id'],
  1142. 'crt_id' => $user['id'],
  1143. 'crt_time' => $time,
  1144. 'upd_time' => $time,
  1145. 'del_time' => 0
  1146. ]);
  1147. }
  1148. // C. 批量插入详情
  1149. $insertDetails = [];
  1150. foreach ($group['details'] as $detail) {
  1151. $detail['main_id'] = $mainId;
  1152. $detail['top_depart_id']= $user['top_depart_id'];
  1153. $detail['crt_time'] = $time;
  1154. $detail['del_time'] = 0;
  1155. $insertDetails[] = $detail;
  1156. }
  1157. if (!empty($insertDetails)) {
  1158. DB::table('monthly_pw_order_details')->insert($insertDetails);
  1159. }
  1160. }
  1161. DB::commit();
  1162. } catch (\Exception $e) {
  1163. DB::rollBack();
  1164. return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
  1165. }
  1166. return [true, ''];
  1167. }
  1168. private function monthPwOrderCheck(&$array, $user, $table_config)
  1169. {
  1170. $keys = array_column($table_config, 'key');
  1171. $codeIdx = array_search('code', $keys);
  1172. $monthIdx = array_search('month', $keys);
  1173. $empIdx = array_search('employee_id', $keys);
  1174. $startIdx = array_search('start_time', $keys);
  1175. $endIdx = array_search('end_time', $keys);
  1176. $numIdx = array_search('total_days', $keys);
  1177. $num2Idx = array_search('rd_total_days', $keys);
  1178. $num3Idx = array_search('total_hours', $keys);
  1179. $num4Idx = array_search('rd_total_hours', $keys);
  1180. // 1. 预加载基础数据
  1181. $allEmpNumbers = array_filter(array_unique(array_column($array, $empIdx)));
  1182. $dbEmps = Employee::where('del_time', 0)
  1183. ->whereIn('number', $allEmpNumbers)
  1184. ->where('top_depart_id', $user['top_depart_id'])
  1185. ->pluck('id', 'number')->toArray();
  1186. $allCodes = array_filter(array_unique(array_column($array, $codeIdx)));
  1187. $dbOrders = DB::table('monthly_pw_order')
  1188. ->whereIn('code', $allCodes)
  1189. ->where('top_depart_id', $user['top_depart_id'])
  1190. ->get()->keyBy('code');
  1191. $errors = [];
  1192. //时间转换
  1193. foreach ($array as $rowIndex => $row) {
  1194. $valMonthRaw = trim($row[$monthIdx] ?? '');
  1195. if ($valMonthRaw === '') continue;
  1196. list($mStatus, $valMonthTs) = $this->convertExcelCellToDate($valMonthRaw);
  1197. if (!$mStatus) {
  1198. $errors[] = "第" . ($rowIndex + 1) . "行:月份格式错误({$valMonthRaw})";
  1199. continue;
  1200. }
  1201. // 标准化并存回数组,这样后面的循环直接拿到的就是时间戳
  1202. $valMonthTs = strtotime(date('Y-m-01', $valMonthTs));
  1203. $array[$rowIndex][$monthIdx] = $valMonthTs;
  1204. $uniqueMonths[] = $valMonthTs;
  1205. }
  1206. $existingMonthsMap = [];
  1207. if (!empty($uniqueMonths)) {
  1208. $existingMonths = DB::table('monthly_pw_order')
  1209. ->where('top_depart_id', $user['top_depart_id'])
  1210. ->where('del_time', 0)
  1211. ->whereIn('month', array_unique($uniqueMonths))
  1212. ->pluck('month')
  1213. ->toArray();
  1214. $existingMonthsMap = array_fill_keys($existingMonths, true);
  1215. }
  1216. $excelAggregator = [];
  1217. $update_map = [];
  1218. foreach ($array as $rowIndex => $row) {
  1219. // 用户看到的行号(假设数据从第2行开始)
  1220. $displayLine = $rowIndex + 1;
  1221. $valCode = trim($row[$codeIdx] ?? '');
  1222. $valMonthTs = trim($row[$monthIdx] ?? '');
  1223. $valEmp = trim($row[$empIdx] ?? '');
  1224. $valStartRaw = $row[$startIdx] ?? '';
  1225. $valEndRaw = $row[$endIdx] ?? '';
  1226. $valNumRaw = $row[$numIdx] ?? 0;
  1227. $valNum2Raw = $row[$num2Idx] ?? 0;
  1228. $valNum3Raw = $row[$num3Idx] ?? 0;
  1229. $valNum4Raw = $row[$num4Idx] ?? 0;
  1230. if (!is_numeric($valMonthTs)) continue;
  1231. // 转换开始日期
  1232. list($sStatus, $startTime) = $this->convertExcelCellToDate($valStartRaw);
  1233. if (!$sStatus) {
  1234. $errors[] = "第{$displayLine}行:开始日期格式错误";
  1235. continue;
  1236. }
  1237. // 转换结束日期
  1238. list($eStatus, $endTime) = $this->convertExcelCellToDate($valEndRaw);
  1239. if (!$eStatus) {
  1240. $errors[] = "第{$displayLine}行:结束日期格式错误";
  1241. continue;
  1242. }
  1243. $aggKey = $valCode ?: "NEW_ORDER_" . $valMonthTs;
  1244. // --- B. 校验单据与月份的一致性 ---
  1245. if ($valCode && isset($dbOrders[$valCode])) {
  1246. $dbOrder = $dbOrders[$valCode];
  1247. if ($dbOrder->month != $valMonthTs) {
  1248. $errors[] = "第{$displayLine}行:单据编码[{$valCode}]对应月份为[" . date('Y-m', $dbOrder->month) . "],与导入月份不符";
  1249. }
  1250. $update_map[$rowIndex] = $dbOrder->id;
  1251. }
  1252. // --- C. 校验人员存在性与单据内唯一性 ---
  1253. if (!isset($dbEmps[$valEmp])) {
  1254. $errors[] = "第{$displayLine}行:人员工号[{$valEmp}]不存在";
  1255. } else {
  1256. if (isset($excelAggregator[$aggKey]['emps'][$valEmp])) {
  1257. $errors[] = "第{$displayLine}行:人员[{$valEmp}]在同一单据中重复";
  1258. }
  1259. $excelAggregator[$aggKey]['emps'][$valEmp] = true;
  1260. if (isset($excelAggregator[$aggKey]['month_ts']) && $excelAggregator[$aggKey]['month_ts'] !== $valMonthTs) {
  1261. $errors[] = "第{$displayLine}行:同组数据的月份不统一";
  1262. }
  1263. $excelAggregator[$aggKey]['month_ts'] = $valMonthTs;
  1264. }
  1265. // --- D. 逻辑校验:日期范围 ---
  1266. if ($startTime > $endTime) {
  1267. $errors[] = "第{$displayLine}行:开始日期大于结束日期";
  1268. } else {
  1269. $monthEndTs = strtotime("+1 month", $valMonthTs) - 1;
  1270. if ($startTime < $valMonthTs || $endTime > $monthEndTs) {
  1271. $errors[] = "第{$displayLine}行:日期超出[ " . date('Y-m', $valMonthTs) . " ]范围";
  1272. }
  1273. }
  1274. // --- E. 月份唯一单据校验 (新增) ---
  1275. if (!$valCode) {
  1276. if (isset($existingMonthsMap[$valMonthTs])) {
  1277. $errors[] = "第{$displayLine}行:月份[ " . date('Y-m', $valMonthTs) . " ]已存在单据,请填编码编辑";
  1278. }
  1279. }
  1280. // --- F. 数字校验
  1281. $res = $this->checkNumber($valNumRaw,0,'non-negative');
  1282. if(! $res['valid']) $errors[] = "第{$displayLine}行出勤总天数:" . $res['error'];
  1283. $res = $this->checkNumber($valNum2Raw,0,'non-negative');
  1284. if(! $res['valid']) $errors[] = "第{$displayLine}行研发出勤总天数:" . $res['error'];
  1285. $res = $this->checkNumber($valNum3Raw,2,'non-negative');
  1286. if(! $res['valid']) $errors[] = "第{$displayLine}行出勤总工时:" . $res['error'];
  1287. $res = $this->checkNumber($valNum4Raw,2,'non-negative');
  1288. if(! $res['valid']) $errors[] = "第{$displayLine}行研发总工时:" . $res['error'];
  1289. // 将转换后的时间戳写回原数组,方便后续写入数据库时直接使用
  1290. $array[$rowIndex][$startIdx] = $startTime;
  1291. $array[$rowIndex][$endIdx] = $endTime;
  1292. }
  1293. $error_string = !empty($errors) ? implode('|', $errors) : "";
  1294. return [$error_string, $update_map, $dbEmps];
  1295. }
  1296. // 设备月度工时单
  1297. public function monthDwOrderImport($array, $user, $other_param)
  1298. {
  1299. $upload = $array[0];
  1300. list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
  1301. if (!$status) return [false, $msg];
  1302. $table_config = $msg;
  1303. unset($array[0]);
  1304. if (empty($array)) return [false, '导入数据不能为空'];
  1305. list($array, $error) = $this->checkCommon($array, $table_config);
  1306. if (!empty($error)) return [0, $error];
  1307. // 2. 详细校验 (这里会返回 update_map 和 dbDevs 映射)
  1308. list($error, $update_map, $dbDevs) = $this->monthDwOrderCheck($array, $user, $table_config);
  1309. if (!empty($error)) return [0, $error];
  1310. $keys = array_column($table_config, 'key');
  1311. $codeIdx = array_search('code', $keys);
  1312. $monthIdx = array_search('month', $keys);
  1313. $empIdx = array_search('device_id', $keys);
  1314. // --- 步骤 1: 数据聚合分组 ---
  1315. // 将 Excel 数据按“单据”维度归类
  1316. $groups = [];
  1317. foreach ($array as $rowIndex => $row) {
  1318. $cCode = trim($row[$codeIdx] ?? '');
  1319. $cMonthTs = $row[$monthIdx]; // 校验阶段已转为时间戳
  1320. // 聚合 Key:有编码用编码,没编码用月份标记
  1321. $aggKey = $cCode ?: "MONTH_" . $cMonthTs;
  1322. if (!isset($groups[$aggKey])) {
  1323. $groups[$aggKey] = [
  1324. 'main_id' => $update_map[$rowIndex] ?? 0, // 如果存在则是编辑
  1325. 'code' => $cCode,
  1326. 'month' => $cMonthTs,
  1327. 'details' => []
  1328. ];
  1329. }
  1330. // 准备详情行数据
  1331. $detailTmp = [];
  1332. foreach ($table_config as $k => $conf) {
  1333. if (!$conf['is_main']) {
  1334. $fieldKey = $conf['key'];
  1335. $fieldVal = $row[$k];
  1336. // 如果是人员,转换为 ID
  1337. if ($fieldKey == 'device_id') {
  1338. $fieldVal = $dbDevs[$fieldVal] ?? 0;
  1339. }
  1340. $detailTmp[$fieldKey] = $fieldVal;
  1341. }
  1342. }
  1343. $groups[$aggKey]['details'][] = $detailTmp;
  1344. }
  1345. // --- 步骤 2: 开启事务写入 ---
  1346. DB::beginTransaction();
  1347. try {
  1348. $time = time();
  1349. foreach ($groups as $aggKey => $group) {
  1350. $mainId = $group['main_id'];
  1351. if ($mainId > 0) {
  1352. // 删除旧详情
  1353. DB::table('monthly_dw_order_details')->where('del_time',0)
  1354. ->where('main_id', $mainId)
  1355. ->update(['del_time' => $time]);
  1356. } else {
  1357. // B. 新增逻辑
  1358. $newCode = $this->generateBillNo([
  1359. 'top_depart_id' => $user['top_depart_id'],
  1360. 'type' => MonthlyPwOrder::Order_type,
  1361. 'period' => date("Ym", $group['month'])
  1362. ]);
  1363. $mainId = DB::table('monthly_dw_order')->insertGetId([
  1364. 'code' => $newCode,
  1365. 'month' => $group['month'],
  1366. 'top_depart_id' => $user['top_depart_id'],
  1367. 'crt_id' => $user['id'],
  1368. 'crt_time' => $time,
  1369. 'upd_time' => $time,
  1370. 'del_time' => 0
  1371. ]);
  1372. }
  1373. // C. 批量插入详情
  1374. $insertDetails = [];
  1375. foreach ($group['details'] as $detail) {
  1376. $detail['main_id'] = $mainId;
  1377. $detail['top_depart_id']= $user['top_depart_id'];
  1378. $detail['crt_time'] = $time;
  1379. $detail['del_time'] = 0;
  1380. $insertDetails[] = $detail;
  1381. }
  1382. if (!empty($insertDetails)) {
  1383. DB::table('monthly_dw_order_details')->insert($insertDetails);
  1384. }
  1385. }
  1386. DB::commit();
  1387. } catch (\Exception $e) {
  1388. DB::rollBack();
  1389. return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
  1390. }
  1391. return [true, ''];
  1392. }
  1393. private function monthDwOrderCheck(&$array, $user, $table_config)
  1394. {
  1395. $keys = array_column($table_config, 'key');
  1396. $codeIdx = array_search('code', $keys);
  1397. $monthIdx = array_search('month', $keys);
  1398. $devIdx = array_search('device_id', $keys);
  1399. $startIdx = array_search('start_time', $keys);
  1400. $endIdx = array_search('end_time', $keys);
  1401. $numIdx = array_search('total_days', $keys);
  1402. $num2Idx = array_search('rd_total_days', $keys);
  1403. $num3Idx = array_search('total_hours', $keys);
  1404. $num4Idx = array_search('rd_total_hours', $keys);
  1405. // 1. 预加载基础数据
  1406. $allEmpNumbers = array_filter(array_unique(array_column($array, $devIdx)));
  1407. $dbDevs = Device::where('del_time', 0)
  1408. ->whereIn('code', $allEmpNumbers)
  1409. ->where('top_depart_id', $user['top_depart_id'])
  1410. ->pluck('id', 'code')->toArray();
  1411. $allCodes = array_filter(array_unique(array_column($array, $codeIdx)));
  1412. $dbOrders = DB::table('monthly_dw_order')
  1413. ->whereIn('code', $allCodes)
  1414. ->where('top_depart_id', $user['top_depart_id'])
  1415. ->get()->keyBy('code');
  1416. $errors = [];
  1417. //时间转换
  1418. foreach ($array as $rowIndex => $row) {
  1419. $valMonthRaw = trim($row[$monthIdx] ?? '');
  1420. if ($valMonthRaw === '') continue;
  1421. list($mStatus, $valMonthTs) = $this->convertExcelCellToDate($valMonthRaw);
  1422. if (!$mStatus) {
  1423. $errors[] = "第" . ($rowIndex + 1) . "行:月份格式错误({$valMonthRaw})";
  1424. continue;
  1425. }
  1426. // 标准化并存回数组,这样后面的循环直接拿到的就是时间戳
  1427. $valMonthTs = strtotime(date('Y-m-01', $valMonthTs));
  1428. $array[$rowIndex][$monthIdx] = $valMonthTs;
  1429. $uniqueMonths[] = $valMonthTs;
  1430. }
  1431. $existingMonthsMap = [];
  1432. if (!empty($uniqueMonths)) {
  1433. $existingMonths = DB::table('monthly_dw_order')
  1434. ->where('top_depart_id', $user['top_depart_id'])
  1435. ->where('del_time', 0)
  1436. ->whereIn('month', array_unique($uniqueMonths))
  1437. ->pluck('month')
  1438. ->toArray();
  1439. $existingMonthsMap = array_fill_keys($existingMonths, true);
  1440. }
  1441. $excelAggregator = [];
  1442. $update_map = [];
  1443. foreach ($array as $rowIndex => $row) {
  1444. // 用户看到的行号(假设数据从第2行开始)
  1445. $displayLine = $rowIndex + 1;
  1446. $valCode = trim($row[$codeIdx] ?? '');
  1447. $valMonthTs = trim($row[$monthIdx] ?? '');
  1448. $valDev = trim($row[$devIdx] ?? '');
  1449. $valStartRaw = $row[$startIdx] ?? '';
  1450. $valEndRaw = $row[$endIdx] ?? '';
  1451. $valNumRaw = $row[$numIdx] ?? 0;
  1452. $valNum2Raw = $row[$num2Idx] ?? 0;
  1453. $valNum3Raw = $row[$num3Idx] ?? 0;
  1454. $valNum4Raw = $row[$num4Idx] ?? 0;
  1455. if (!is_numeric($valMonthTs)) continue;
  1456. // 转换开始日期
  1457. list($sStatus, $startTime) = $this->convertExcelCellToDate($valStartRaw);
  1458. if (!$sStatus) {
  1459. $errors[] = "第{$displayLine}行:开始日期格式错误";
  1460. continue;
  1461. }
  1462. // 转换结束日期
  1463. list($eStatus, $endTime) = $this->convertExcelCellToDate($valEndRaw);
  1464. if (!$eStatus) {
  1465. $errors[] = "第{$displayLine}行:结束日期格式错误";
  1466. continue;
  1467. }
  1468. $aggKey = $valCode ?: "NEW_ORDER_" . $valMonthTs;
  1469. // --- B. 校验单据与月份的一致性 ---
  1470. if ($valCode && isset($dbOrders[$valCode])) {
  1471. $dbOrder = $dbOrders[$valCode];
  1472. if ($dbOrder->month != $valMonthTs) {
  1473. $errors[] = "第{$displayLine}行:单据编码[{$valCode}]对应月份为[" . date('Y-m', $dbOrder->month) . "],与导入月份不符";
  1474. }
  1475. $update_map[$rowIndex] = $dbOrder->id;
  1476. }
  1477. // --- C. 校验设备存在性与单据内唯一性 ---
  1478. if (!isset($dbDevs[$valDev])) {
  1479. $errors[] = "第{$displayLine}行:设备的资产编码[{$valDev}]不存在";
  1480. } else {
  1481. if (isset($excelAggregator[$aggKey]['devs'][$valDev])) {
  1482. $errors[] = "第{$displayLine}行:设备的资产编码[{$valDev}]在同一单据中重复";
  1483. }
  1484. $excelAggregator[$aggKey]['devs'][$valDev] = true;
  1485. if (isset($excelAggregator[$aggKey]['month_ts']) && $excelAggregator[$aggKey]['month_ts'] !== $valMonthTs) {
  1486. $errors[] = "第{$displayLine}行:同组数据的月份不统一";
  1487. }
  1488. $excelAggregator[$aggKey]['month_ts'] = $valMonthTs;
  1489. }
  1490. // --- D. 逻辑校验:日期范围 ---
  1491. if ($startTime > $endTime) {
  1492. $errors[] = "第{$displayLine}行:开始日期大于结束日期";
  1493. } else {
  1494. $monthEndTs = strtotime("+1 month", $valMonthTs) - 1;
  1495. if ($startTime < $valMonthTs || $endTime > $monthEndTs) {
  1496. $errors[] = "第{$displayLine}行:日期超出[ " . date('Y-m', $valMonthTs) . " ]范围";
  1497. }
  1498. }
  1499. // --- E. 月份唯一单据校验 (新增) ---
  1500. if (!$valCode) {
  1501. if (isset($existingMonthsMap[$valMonthTs])) {
  1502. $errors[] = "第{$displayLine}行:月份[ " . date('Y-m', $valMonthTs) . " ]已存在单据,请填编码编辑";
  1503. }
  1504. }
  1505. // --- F. 数字校验
  1506. $res = $this->checkNumber($valNumRaw,0,'non-negative');
  1507. if(! $res['valid']) $errors[] = "第{$displayLine}行出勤总天数:" . $res['error'];
  1508. $res = $this->checkNumber($valNum2Raw,0,'non-negative');
  1509. if(! $res['valid']) $errors[] = "第{$displayLine}行研发出勤总天数:" . $res['error'];
  1510. $res = $this->checkNumber($valNum3Raw,2,'non-negative');
  1511. if(! $res['valid']) $errors[] = "第{$displayLine}行出勤总工时:" . $res['error'];
  1512. $res = $this->checkNumber($valNum4Raw,2,'non-negative');
  1513. if(! $res['valid']) $errors[] = "第{$displayLine}行研发总工时:" . $res['error'];
  1514. // 将转换后的时间戳写回原数组,方便后续写入数据库时直接使用
  1515. $array[$rowIndex][$startIdx] = $startTime;
  1516. $array[$rowIndex][$endIdx] = $endTime;
  1517. }
  1518. $error_string = !empty($errors) ? implode('|', $errors) : "";
  1519. return [$error_string, $update_map, $dbDevs];
  1520. }
  1521. // 人员月度工资单
  1522. public function monthPsOrderImport($array, $user, $other_param)
  1523. {
  1524. $upload = $array[0];
  1525. list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
  1526. if (!$status) return [false, $msg];
  1527. $table_config = $msg;
  1528. unset($array[0]);
  1529. if (empty($array)) return [false, '导入数据不能为空'];
  1530. list($array, $error) = $this->checkCommon($array, $table_config);
  1531. if (!empty($error)) return [0, $error];
  1532. // 2. 详细校验 (这里会返回 update_map 和 dbEmps 映射)
  1533. list($error, $update_map, $dbEmps) = $this->monthPsOrderCheck($array, $user, $table_config);
  1534. if (!empty($error)) return [0, $error];
  1535. $keys = array_column($table_config, 'key');
  1536. $codeIdx = array_search('code', $keys);
  1537. $monthIdx = array_search('month', $keys);
  1538. $empIdx = array_search('employee_id', $keys);
  1539. // --- 步骤 1: 数据聚合分组 ---
  1540. // 将 Excel 数据按“单据”维度归类
  1541. $groups = [];
  1542. foreach ($array as $rowIndex => $row) {
  1543. $cCode = trim($row[$codeIdx] ?? '');
  1544. $cMonthTs = $row[$monthIdx]; // 校验阶段已转为时间戳
  1545. // 聚合 Key:有编码用编码,没编码用月份标记
  1546. $aggKey = $cCode ?: "MONTH_" . $cMonthTs;
  1547. if (!isset($groups[$aggKey])) {
  1548. $groups[$aggKey] = [
  1549. 'main_id' => $update_map[$rowIndex] ?? 0, // 如果存在则是编辑
  1550. 'code' => $cCode,
  1551. 'month' => $cMonthTs,
  1552. 'details' => []
  1553. ];
  1554. }
  1555. // 准备详情行数据
  1556. $detailTmp = [];
  1557. foreach ($table_config as $k => $conf) {
  1558. if (!$conf['is_main']) {
  1559. $fieldKey = $conf['key'];
  1560. $fieldVal = $row[$k];
  1561. // 如果是人员,转换为 ID
  1562. if ($fieldKey == 'employee_id') {
  1563. $fieldVal = $dbEmps[$fieldVal] ?? 0;
  1564. }
  1565. $detailTmp[$fieldKey] = $fieldVal;
  1566. }
  1567. }
  1568. $groups[$aggKey]['details'][] = $detailTmp;
  1569. }
  1570. // --- 步骤 2: 开启事务写入 ---
  1571. DB::beginTransaction();
  1572. try {
  1573. $time = time();
  1574. foreach ($groups as $aggKey => $group) {
  1575. $mainId = $group['main_id'];
  1576. if ($mainId > 0) {
  1577. // 删除旧详情
  1578. DB::table('monthly_ps_order_details')->where('del_time',0)
  1579. ->where('main_id', $mainId)
  1580. ->update(['del_time' => $time]);
  1581. } else {
  1582. // B. 新增逻辑
  1583. $newCode = $this->generateBillNo([
  1584. 'top_depart_id' => $user['top_depart_id'],
  1585. 'type' => MonthlyPsOrder::Order_type,
  1586. 'period' => date("Ym", $group['month'])
  1587. ]);
  1588. $mainId = DB::table('monthly_ps_order')->insertGetId([
  1589. 'code' => $newCode,
  1590. 'month' => $group['month'],
  1591. 'top_depart_id' => $user['top_depart_id'],
  1592. 'crt_id' => $user['id'],
  1593. 'crt_time' => $time,
  1594. 'upd_time' => $time,
  1595. 'del_time' => 0
  1596. ]);
  1597. }
  1598. // C. 批量插入详情
  1599. $insertDetails = [];
  1600. foreach ($group['details'] as $detail) {
  1601. $detail['main_id'] = $mainId;
  1602. $detail['top_depart_id']= $user['top_depart_id'];
  1603. $detail['crt_time'] = $time;
  1604. $detail['del_time'] = 0;
  1605. $insertDetails[] = $detail;
  1606. }
  1607. if (!empty($insertDetails)) {
  1608. DB::table('monthly_ps_order_details')->insert($insertDetails);
  1609. }
  1610. }
  1611. DB::commit();
  1612. } catch (\Exception $e) {
  1613. DB::rollBack();
  1614. return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
  1615. }
  1616. return [true, ''];
  1617. }
  1618. private function monthPsOrderCheck(&$array, $user, $table_config)
  1619. {
  1620. $keys = array_column($table_config, 'key');
  1621. $codeIdx = array_search('code', $keys);
  1622. $monthIdx = array_search('month', $keys);
  1623. $empIdx = array_search('employee_id', $keys);
  1624. $numIdx = array_search('salary', $keys);
  1625. $num2Idx = array_search('social_insurance', $keys);
  1626. $num3Idx = array_search('public_housing_fund', $keys);
  1627. // 1. 预加载基础数据
  1628. $allEmpNumbers = array_filter(array_unique(array_column($array, $empIdx)));
  1629. $dbEmps = Employee::where('del_time', 0)
  1630. ->whereIn('number', $allEmpNumbers)
  1631. ->where('top_depart_id', $user['top_depart_id'])
  1632. ->pluck('id', 'number')->toArray();
  1633. $allCodes = array_filter(array_unique(array_column($array, $codeIdx)));
  1634. $dbOrders = DB::table('monthly_ps_order')
  1635. ->whereIn('code', $allCodes)
  1636. ->where('top_depart_id', $user['top_depart_id'])
  1637. ->get()->keyBy('code');
  1638. $errors = [];
  1639. //时间转换
  1640. foreach ($array as $rowIndex => $row) {
  1641. $valMonthRaw = trim($row[$monthIdx] ?? '');
  1642. if ($valMonthRaw === '') continue;
  1643. list($mStatus, $valMonthTs) = $this->convertExcelCellToDate($valMonthRaw);
  1644. if (!$mStatus) {
  1645. $errors[] = "第" . ($rowIndex + 1) . "行:月份格式错误({$valMonthRaw})";
  1646. continue;
  1647. }
  1648. // 标准化并存回数组,这样后面的循环直接拿到的就是时间戳
  1649. $valMonthTs = strtotime(date('Y-m-01', $valMonthTs));
  1650. $array[$rowIndex][$monthIdx] = $valMonthTs;
  1651. $uniqueMonths[] = $valMonthTs;
  1652. }
  1653. $existingMonthsMap = [];
  1654. if (!empty($uniqueMonths)) {
  1655. $existingMonths = DB::table('monthly_ps_order')
  1656. ->where('top_depart_id', $user['top_depart_id'])
  1657. ->where('del_time', 0)
  1658. ->whereIn('month', array_unique($uniqueMonths))
  1659. ->pluck('month')
  1660. ->toArray();
  1661. $existingMonthsMap = array_fill_keys($existingMonths, true);
  1662. }
  1663. $excelAggregator = [];
  1664. $update_map = [];
  1665. foreach ($array as $rowIndex => $row) {
  1666. // 用户看到的行号(假设数据从第2行开始)
  1667. $displayLine = $rowIndex + 1;
  1668. $valCode = trim($row[$codeIdx] ?? '');
  1669. $valMonthTs = trim($row[$monthIdx] ?? '');
  1670. $valEmp = trim($row[$empIdx] ?? '');
  1671. $valNumRaw = $row[$numIdx] ?? 0;
  1672. $valNum2Raw = $row[$num2Idx] ?? 0;
  1673. $valNum3Raw = $row[$num3Idx] ?? 0;
  1674. if (!is_numeric($valMonthTs)) continue;
  1675. $aggKey = $valCode ?: "NEW_ORDER_" . $valMonthTs;
  1676. // --- B. 校验单据与月份的一致性 ---
  1677. if ($valCode && isset($dbOrders[$valCode])) {
  1678. $dbOrder = $dbOrders[$valCode];
  1679. if ($dbOrder->month != $valMonthTs) {
  1680. $errors[] = "第{$displayLine}行:单据编码[{$valCode}]对应月份为[" . date('Y-m', $dbOrder->month) . "],与导入月份不符";
  1681. }
  1682. $update_map[$rowIndex] = $dbOrder->id;
  1683. }
  1684. // --- C. 校验人员存在性与单据内唯一性 ---
  1685. if (!isset($dbEmps[$valEmp])) {
  1686. $errors[] = "第{$displayLine}行:人员工号[{$valEmp}]不存在";
  1687. } else {
  1688. if (isset($excelAggregator[$aggKey]['emps'][$valEmp])) {
  1689. $errors[] = "第{$displayLine}行:人员[{$valEmp}]在同一单据中重复";
  1690. }
  1691. $excelAggregator[$aggKey]['emps'][$valEmp] = true;
  1692. if (isset($excelAggregator[$aggKey]['month_ts']) && $excelAggregator[$aggKey]['month_ts'] !== $valMonthTs) {
  1693. $errors[] = "第{$displayLine}行:同组数据的月份不统一";
  1694. }
  1695. $excelAggregator[$aggKey]['month_ts'] = $valMonthTs;
  1696. }
  1697. // --- E. 月份唯一单据校验 (新增) ---
  1698. if (!$valCode) {
  1699. if (isset($existingMonthsMap[$valMonthTs])) {
  1700. $errors[] = "第{$displayLine}行:月份[ " . date('Y-m', $valMonthTs) . " ]已存在单据,请填编码编辑";
  1701. }
  1702. }
  1703. // --- F. 数字校验
  1704. $res = $this->checkNumber($valNumRaw,0,'non-negative');
  1705. if(! $res['valid']) $errors[] = "第{$displayLine}行工资总额:" . $res['error'];
  1706. $res = $this->checkNumber($valNum2Raw,0,'non-negative');
  1707. if(! $res['valid']) $errors[] = "第{$displayLine}行社保:" . $res['error'];
  1708. $res = $this->checkNumber($valNum3Raw,2,'non-negative');
  1709. if(! $res['valid']) $errors[] = "第{$displayLine}行公积金:" . $res['error'];
  1710. }
  1711. $error_string = !empty($errors) ? implode('|', $errors) : "";
  1712. return [$error_string, $update_map, $dbEmps];
  1713. }
  1714. // 设备月度折旧单
  1715. public function monthDdOrderImport($array, $user, $other_param)
  1716. {
  1717. $upload = $array[0];
  1718. list($status, $msg) = $this->compareTableAndReturn($upload, $other_param);
  1719. if (!$status) return [false, $msg];
  1720. $table_config = $msg;
  1721. unset($array[0]);
  1722. if (empty($array)) return [false, '导入数据不能为空'];
  1723. list($array, $error) = $this->checkCommon($array, $table_config);
  1724. if (!empty($error)) return [0, $error];
  1725. // 2. 详细校验 (这里会返回 update_map 和 dbDevs 映射)
  1726. list($error, $update_map, $dbDevs) = $this->monthDdOrderCheck($array, $user, $table_config);
  1727. if (!empty($error)) return [0, $error];
  1728. $keys = array_column($table_config, 'key');
  1729. $codeIdx = array_search('code', $keys);
  1730. $monthIdx = array_search('month', $keys);
  1731. $empIdx = array_search('device_id', $keys);
  1732. // --- 步骤 1: 数据聚合分组 ---
  1733. // 将 Excel 数据按“单据”维度归类
  1734. $groups = [];
  1735. foreach ($array as $rowIndex => $row) {
  1736. $cCode = trim($row[$codeIdx] ?? '');
  1737. $cMonthTs = $row[$monthIdx]; // 校验阶段已转为时间戳
  1738. // 聚合 Key:有编码用编码,没编码用月份标记
  1739. $aggKey = $cCode ?: "MONTH_" . $cMonthTs;
  1740. if (!isset($groups[$aggKey])) {
  1741. $groups[$aggKey] = [
  1742. 'main_id' => $update_map[$rowIndex] ?? 0, // 如果存在则是编辑
  1743. 'code' => $cCode,
  1744. 'month' => $cMonthTs,
  1745. 'details' => []
  1746. ];
  1747. }
  1748. // 准备详情行数据
  1749. $detailTmp = [];
  1750. foreach ($table_config as $k => $conf) {
  1751. if (!$conf['is_main']) {
  1752. $fieldKey = $conf['key'];
  1753. $fieldVal = $row[$k];
  1754. // 如果是人员,转换为 ID
  1755. if ($fieldKey == 'device_id') {
  1756. $fieldVal = $dbDevs[$fieldVal] ?? 0;
  1757. }
  1758. $detailTmp[$fieldKey] = $fieldVal;
  1759. }
  1760. }
  1761. $groups[$aggKey]['details'][] = $detailTmp;
  1762. }
  1763. // --- 步骤 2: 开启事务写入 ---
  1764. DB::beginTransaction();
  1765. try {
  1766. $time = time();
  1767. foreach ($groups as $aggKey => $group) {
  1768. $mainId = $group['main_id'];
  1769. if ($mainId > 0) {
  1770. // 删除旧详情
  1771. DB::table('monthly_dd_order_details')->where('del_time',0)
  1772. ->where('main_id', $mainId)
  1773. ->update(['del_time' => $time]);
  1774. } else {
  1775. // B. 新增逻辑
  1776. $newCode = $this->generateBillNo([
  1777. 'top_depart_id' => $user['top_depart_id'],
  1778. 'type' => MonthlyDdOrder::Order_type,
  1779. 'period' => date("Ym", $group['month'])
  1780. ]);
  1781. $mainId = DB::table('monthly_dd_order')->insertGetId([
  1782. 'code' => $newCode,
  1783. 'month' => $group['month'],
  1784. 'top_depart_id' => $user['top_depart_id'],
  1785. 'crt_id' => $user['id'],
  1786. 'crt_time' => $time,
  1787. 'upd_time' => $time,
  1788. 'del_time' => 0
  1789. ]);
  1790. }
  1791. // C. 批量插入详情
  1792. $insertDetails = [];
  1793. foreach ($group['details'] as $detail) {
  1794. $detail['main_id'] = $mainId;
  1795. $detail['top_depart_id']= $user['top_depart_id'];
  1796. $detail['crt_time'] = $time;
  1797. $detail['del_time'] = 0;
  1798. $insertDetails[] = $detail;
  1799. }
  1800. if (!empty($insertDetails)) {
  1801. DB::table('monthly_dd_order_details')->insert($insertDetails);
  1802. }
  1803. }
  1804. DB::commit();
  1805. } catch (\Exception $e) {
  1806. DB::rollBack();
  1807. return [false, "失败:" . $e->getMessage() . " (行号:" . $e->getLine() . ")"];
  1808. }
  1809. return [true, ''];
  1810. }
  1811. private function monthDdOrderCheck(&$array, $user, $table_config)
  1812. {
  1813. $keys = array_column($table_config, 'key');
  1814. $codeIdx = array_search('code', $keys);
  1815. $monthIdx = array_search('month', $keys);
  1816. $devIdx = array_search('device_id', $keys);
  1817. $numIdx = array_search('depreciation_amount', $keys);
  1818. // 1. 预加载基础数据
  1819. $allEmpNumbers = array_filter(array_unique(array_column($array, $devIdx)));
  1820. $dbDevs = Device::where('del_time', 0)
  1821. ->whereIn('code', $allEmpNumbers)
  1822. ->where('top_depart_id', $user['top_depart_id'])
  1823. ->pluck('id', 'code')->toArray();
  1824. $allCodes = array_filter(array_unique(array_column($array, $codeIdx)));
  1825. $dbOrders = DB::table('monthly_dd_order')
  1826. ->whereIn('code', $allCodes)
  1827. ->where('top_depart_id', $user['top_depart_id'])
  1828. ->get()->keyBy('code');
  1829. $errors = [];
  1830. //时间转换
  1831. foreach ($array as $rowIndex => $row) {
  1832. $valMonthRaw = trim($row[$monthIdx] ?? '');
  1833. if ($valMonthRaw === '') continue;
  1834. list($mStatus, $valMonthTs) = $this->convertExcelCellToDate($valMonthRaw);
  1835. if (!$mStatus) {
  1836. $errors[] = "第" . ($rowIndex + 1) . "行:月份格式错误({$valMonthRaw})";
  1837. continue;
  1838. }
  1839. // 标准化并存回数组,这样后面的循环直接拿到的就是时间戳
  1840. $valMonthTs = strtotime(date('Y-m-01', $valMonthTs));
  1841. $array[$rowIndex][$monthIdx] = $valMonthTs;
  1842. $uniqueMonths[] = $valMonthTs;
  1843. }
  1844. $existingMonthsMap = [];
  1845. if (!empty($uniqueMonths)) {
  1846. $existingMonths = DB::table('monthly_dd_order')
  1847. ->where('top_depart_id', $user['top_depart_id'])
  1848. ->where('del_time', 0)
  1849. ->whereIn('month', array_unique($uniqueMonths))
  1850. ->pluck('month')
  1851. ->toArray();
  1852. $existingMonthsMap = array_fill_keys($existingMonths, true);
  1853. }
  1854. $excelAggregator = [];
  1855. $update_map = [];
  1856. foreach ($array as $rowIndex => $row) {
  1857. // 用户看到的行号(假设数据从第2行开始)
  1858. $displayLine = $rowIndex + 1;
  1859. $valCode = trim($row[$codeIdx] ?? '');
  1860. $valMonthTs = trim($row[$monthIdx] ?? '');
  1861. $valDev = trim($row[$devIdx] ?? '');
  1862. $valNumRaw = $row[$numIdx] ?? 0;
  1863. if (!is_numeric($valMonthTs)) continue;
  1864. $aggKey = $valCode ?: "NEW_ORDER_" . $valMonthTs;
  1865. // --- B. 校验单据与月份的一致性 ---
  1866. if ($valCode && isset($dbOrders[$valCode])) {
  1867. $dbOrder = $dbOrders[$valCode];
  1868. if ($dbOrder->month != $valMonthTs) {
  1869. $errors[] = "第{$displayLine}行:单据编码[{$valCode}]对应月份为[" . date('Y-m', $dbOrder->month) . "],与导入月份不符";
  1870. }
  1871. $update_map[$rowIndex] = $dbOrder->id;
  1872. }
  1873. // --- C. 校验设备存在性与单据内唯一性 ---
  1874. if (!isset($dbDevs[$valDev])) {
  1875. $errors[] = "第{$displayLine}行:设备的资产编码[{$valDev}]不存在";
  1876. } else {
  1877. if (isset($excelAggregator[$aggKey]['devs'][$valDev])) {
  1878. $errors[] = "第{$displayLine}行:设备的资产编码[{$valDev}]在同一单据中重复";
  1879. }
  1880. $excelAggregator[$aggKey]['devs'][$valDev] = true;
  1881. if (isset($excelAggregator[$aggKey]['month_ts']) && $excelAggregator[$aggKey]['month_ts'] !== $valMonthTs) {
  1882. $errors[] = "第{$displayLine}行:同组数据的月份不统一";
  1883. }
  1884. $excelAggregator[$aggKey]['month_ts'] = $valMonthTs;
  1885. }
  1886. // --- E. 月份唯一单据校验 (新增) ---
  1887. if (!$valCode) {
  1888. if (isset($existingMonthsMap[$valMonthTs])) {
  1889. $errors[] = "第{$displayLine}行:月份[ " . date('Y-m', $valMonthTs) . " ]已存在单据,请填编码编辑";
  1890. }
  1891. }
  1892. // --- F. 数字校验
  1893. $res = $this->checkNumber($valNumRaw,2,'non-negative');
  1894. if(! $res['valid']) $errors[] = "第{$displayLine}行月折旧额:" . $res['error'];
  1895. }
  1896. $error_string = !empty($errors) ? implode('|', $errors) : "";
  1897. return [$error_string, $update_map, $dbDevs];
  1898. }
  1899. //公共校验 -----------------------------------------
  1900. private function checkCommon($array, $table_config) {
  1901. $error = [];
  1902. $uniqueCheck = []; // 格式:[$column_index => [$value => $first_line]]
  1903. foreach ($array as $line => $row) {
  1904. $rowData = array_filter($row);
  1905. if (empty($rowData)) {
  1906. unset($array[$line]);
  1907. continue;
  1908. }
  1909. foreach ($row as $colIndex => $value) {
  1910. $value = trim($value);
  1911. $config = $table_config[$colIndex] ?? null;
  1912. // 1. 基础存在性检查
  1913. if (!$config) {
  1914. $error[] = "第{$line}行第{$colIndex}列配置不存在";
  1915. continue;
  1916. }
  1917. $fieldName = $config['value'];
  1918. // 2. 必填校验
  1919. if (!empty($config['required']) && ($value === '' || !isset($value))) {
  1920. $error[] = "第{$line}行:[{$fieldName}] 必填";
  1921. }
  1922. // 3. 默认值填充
  1923. if ($value === '' && isset($config['default'])) {
  1924. $value = $config['default'];
  1925. }
  1926. // 4. 唯一性校验(重点:一次遍历解决)
  1927. if (!empty($config['unique']) && $value !== '') {
  1928. if (isset($uniqueCheck[$colIndex][$value])) {
  1929. $prevLine = $uniqueCheck[$colIndex][$value];
  1930. $error[] = "第{$line}行:[{$fieldName}] 与第{$prevLine}行重复,重复值:{$value}";
  1931. } else {
  1932. // 记录该值第一次出现的位置
  1933. $uniqueCheck[$colIndex][$value] = $line;
  1934. }
  1935. }
  1936. $row[$colIndex] = $value;
  1937. }
  1938. $array[$line] = $row;
  1939. }
  1940. $error_string = !empty($error) ? implode('|', $error) : "";
  1941. return [$array, $error_string];
  1942. }
  1943. //模板校验 -----------------------------------------
  1944. private function compareTableAndReturn($upload, $param){
  1945. if(empty($upload)) return [false, '表头不能为空'];
  1946. $config = $this->getTableConfig($param['type']);
  1947. $config_array = $config['array'];
  1948. if(empty($config_array)) return [false, '导入配置表头文件不存在'];
  1949. foreach ($config_array as $key => $value){
  1950. $key_position = $key + 1;
  1951. if(! isset($upload[$key])) return [false, "第" . $key_position . "列表头缺失"];
  1952. $tmp_v = trim($upload[$key]);
  1953. if($tmp_v != $value['value']) return [false, "第" . $key_position . "列表头与模板不符合,请重新下载模板"];
  1954. }
  1955. return [true, $config_array];
  1956. }
  1957. //转换日期 ------------------------------------------
  1958. function convertExcelCellToDate($cellValue) {
  1959. // 尝试将单元格值转换为浮点数(Excel 日期序列号)
  1960. $excelTimestamp = filter_var($cellValue, FILTER_VALIDATE_FLOAT);
  1961. if ($excelTimestamp !== false && $excelTimestamp > 0) {
  1962. // 如果成功转换并且值大于0,则认为是Excel日期序列号
  1963. try {
  1964. $dateTimeObject = Date::excelToDateTimeObject($cellValue);
  1965. // if ($dateTimeObject->format('H:i:s') === '00:00:00') {
  1966. // // 如果是,则将时间设置为 '23:59:00'
  1967. // $dateTimeObject->setTime(23, 59);
  1968. // }
  1969. // 现在你可以格式化这个日期了
  1970. $formattedDate = $dateTimeObject->format('Y-m-d');
  1971. if(! strtotime($formattedDate)) return [false, ''];
  1972. return [true, strtotime($formattedDate)];
  1973. } catch (\Exception $e) {
  1974. // 处理转换失败的情况
  1975. return [false, '单元格日期格式转换时间戳失败'];
  1976. }
  1977. }
  1978. // 如果不是有效的浮点数,则尝试按照多种日期格式解析
  1979. if(! strtotime($cellValue)) return [false, '单元格文本格式转换时间戳失败'];
  1980. return [true, strtotime($cellValue)];
  1981. }
  1982. }