StatCgClass.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <?php
  2. /**
  3. * 采购统计:花材排行 / 采购人业绩。
  4. * 口径对齐销售隔天售后:入库日用 nextRefundNum 算净量;通过日再扣隔天金额/数量(可为负,笔数不减)。
  5. */
  6. namespace bizGhs\stat\classes;
  7. use bizGhs\cg\classes\CgPurchaseNextDayClass;
  8. use bizGhs\order\classes\PurchaseOrderClass;
  9. use bizGhs\order\classes\PurchaseOrderItemClass;
  10. use bizGhs\product\classes\ProductClass;
  11. use common\components\arrayUtil;
  12. use common\components\dateUtil;
  13. use common\components\util;
  14. use Yii;
  15. use bizGhs\base\classes\BaseClass;
  16. class StatCgClass extends BaseClass
  17. {
  18. /**
  19. * 采购花材排行(兼返回采购人按明细汇总,供兼容旧调用)
  20. * @param int $mainId
  21. * @return array{itemList:array,staffCg:array}
  22. */
  23. public static function statCg($mainId)
  24. {
  25. $get = Yii::$app->request->get();
  26. $where = [];
  27. $where['mainId'] = $mainId;
  28. $searchTime = isset($get['searchTime']) && !empty($get['searchTime']) ? $get['searchTime'] : 'today';
  29. $startTime = $get['startTime'] ?? '';
  30. $endTime = $get['endTime'] ?? '';
  31. $period = dateUtil::formatTime($searchTime, $startTime, $endTime, true);
  32. $start = $period['startTime'];
  33. $end = $period['endTime'];
  34. $where['time'] = ['between', [$start, $end]];
  35. $currentStartDate = date('Y-m-d', strtotime($start));
  36. $currentEndDate = date('Y-m-d', strtotime($end));
  37. $currentStartTime = $currentStartDate . ' 00:00:00';
  38. $currentEndTime = $currentEndDate . ' 23:59:59';
  39. $sort = $get['sort'] ?? 'num';
  40. $rank = 'num';
  41. if ($sort == 'amount') {
  42. $rank = 'amount';
  43. }
  44. $productInfo = ProductClass::getAllByCondition(['mainId' => $mainId], null, 'id,name,py', 'id');
  45. $cgWhere = ['mainId' => $mainId, 'status' => 4];
  46. $cgWhere['entryTime'] = ['between', [$currentStartTime, $currentEndTime]];
  47. $cgList = PurchaseOrderClass::getAllByCondition($cgWhere, null, '*');
  48. $arr = [];
  49. $cgData = [];
  50. if (!empty($cgList)) {
  51. foreach ($cgList as $cgKey => $cgInfo) {
  52. $orderSn = $cgInfo['orderSn'] ?? '';
  53. $cgStaffId = $cgInfo['cgStaffId'] ?? 0;
  54. $cgStaffName = $cgInfo['cgStaffName'] ?? '';
  55. $itemList = PurchaseOrderItemClass::getAllByCondition(['orderSn' => $orderSn], null, '*', null, true);
  56. if (!empty($itemList)) {
  57. foreach ($itemList as $key => $val) {
  58. $productId = $val->productId ?? 0;
  59. $num = $val->itemNum ?? 0;
  60. $refundNum = $val->refundNum ?? 0;
  61. // 入库日净量:隔天已退记在 nextRefundNum,不在入库日扣
  62. $remainNum = CgPurchaseNextDayClass::payDayRemainNum(
  63. $num,
  64. $refundNum,
  65. $val->nextRefundNum ?? 0
  66. );
  67. $unitPrice = $val->bigPrice ?? 0;
  68. $name = $productInfo[$productId]['name'] ?? '';
  69. $py = $productInfo[$productId]['py'] ?? '';
  70. $totalPrice = bcmul($remainNum, $unitPrice, 2);
  71. if (isset($arr[$productId])) {
  72. $arr[$productId]['num'] = bcadd($arr[$productId]['num'], $remainNum);
  73. $arr[$productId]['amount'] = bcadd($arr[$productId]['amount'], $totalPrice, 2);
  74. } else {
  75. $arr[$productId]['num'] = $remainNum;
  76. $arr[$productId]['amount'] = $totalPrice;
  77. $arr[$productId]['py'] = $py;
  78. $arr[$productId]['name'] = $name;
  79. }
  80. if (isset($cgData[$cgStaffId])) {
  81. $cgData[$cgStaffId]['amount'] = bcadd($cgData[$cgStaffId]['amount'], $totalPrice, 2);
  82. $cgData[$cgStaffId]['itemNum'] = bcadd($cgData[$cgStaffId]['itemNum'], $remainNum);
  83. } else {
  84. $cgData[$cgStaffId]['cgStaffId'] = $cgStaffId;
  85. $cgData[$cgStaffId]['cgStaffName'] = $cgStaffName;
  86. $cgData[$cgStaffId]['amount'] = $totalPrice;
  87. $cgData[$cgStaffId]['num'] = 0;
  88. $cgData[$cgStaffId]['itemNum'] = $remainNum;
  89. }
  90. }
  91. if (isset($cgData[$cgStaffId])) {
  92. $cgData[$cgStaffId]['num'] = bcadd($cgData[$cgStaffId]['num'], 1);
  93. } else {
  94. $cgData[$cgStaffId]['num'] = 1;
  95. }
  96. }
  97. }
  98. }
  99. // 隔天售后:按通过日扣花材数量/金额;无当日入库也建负行
  100. self::applyNextDayAdjustToCgItemRows($arr, $productInfo, $mainId, $currentStartTime, $currentEndTime);
  101. $nextDayDeduct = CgPurchaseNextDayClass::sumAmountByMainAndTime($mainId, $currentStartTime, $currentEndTime);
  102. if (!empty($arr)) {
  103. // 去掉数量金额都为 0 的行,保留负数行便于看见隔天扣减
  104. $arr = array_values(array_filter($arr, function ($row) {
  105. $num = bcadd((string)($row['num'] ?? '0'), '0', 2);
  106. $amount = bcadd((string)($row['amount'] ?? '0'), '0', 2);
  107. return bccomp($num, '0', 2) != 0 || bccomp($amount, '0', 2) != 0;
  108. }));
  109. $arr = arrayUtil::arraySort($arr, $rank);
  110. }
  111. if (!empty($cgData)) {
  112. $cgData = array_values($cgData);
  113. }
  114. $export = $get['export']??0;
  115. if($export == 1){
  116. self::exportCgItem($arr,$mainId);
  117. }
  118. return ['itemList' => $arr, 'staffCg' => $cgData, 'nextDayDeduct' => $nextDayDeduct];
  119. }
  120. /**
  121. * 通过日扣减采购花材:退货退款扣数量+金额;仅退款只扣金额(按原单占比分摊)
  122. *
  123. * @param array $arr productId => 行
  124. * @param array $productInfo 花材字典
  125. * @param int $mainId
  126. * @param string $startTime
  127. * @param string $endTime
  128. */
  129. private static function applyNextDayAdjustToCgItemRows(
  130. &$arr,
  131. $productInfo,
  132. $mainId,
  133. $startTime,
  134. $endTime
  135. ) {
  136. $applyDelta = function ($pidMap, $sign) use (&$arr, $productInfo) {
  137. foreach ($pidMap as $pid => $row) {
  138. $deltaNum = bcmul((string)($row['num'] ?? '0'), (string)$sign, 2);
  139. $deltaAmt = bcmul((string)($row['amount'] ?? '0'), (string)$sign, 2);
  140. if (bccomp($deltaAmt, '0', 2) == 0 && bccomp($deltaNum, '0', 2) == 0) {
  141. continue;
  142. }
  143. if (!isset($arr[$pid])) {
  144. $arr[$pid] = [
  145. 'num' => '0.00',
  146. 'amount' => '0.00',
  147. 'py' => $productInfo[$pid]['py'] ?? '',
  148. 'name' => $productInfo[$pid]['name'] ?? '已删除',
  149. ];
  150. }
  151. $arr[$pid]['num'] = bcadd((string)($arr[$pid]['num'] ?? 0), $deltaNum, 2);
  152. $arr[$pid]['amount'] = bcadd((string)($arr[$pid]['amount'] ?? 0), $deltaAmt, 2);
  153. }
  154. };
  155. $applyDelta(CgPurchaseNextDayClass::sumItemByProduct($mainId, $startTime, $endTime), -1);
  156. $applyDelta(CgPurchaseNextDayClass::sumMoneyOnlyAmountByProduct($mainId, $startTime, $endTime), -1);
  157. }
  158. public static function exportCgItem($list, $mainId)
  159. {
  160. $phpExcelFile = Yii::getAlias("@vendor/phpoffice/phpexcel/");
  161. require_once($phpExcelFile . 'Classes/PHPExcel.php');
  162. $objPHPExcel = new \PHPExcel();
  163. $objPHPExcel->getProperties()->setCreator("Maarten Balliauw")
  164. ->setLastModifiedBy("Maarten Balliauw")
  165. ->setTitle("Office 2007 XLSX Document")
  166. ->setSubject("Office 2007 XLSX Document")
  167. ->setDescription("Test document for Office 2007 XLSX, generated using PHP classes.")
  168. ->setKeywords("office 2007 openxml php")
  169. ->setCategory("file");
  170. $ghsTitle = '供货商';
  171. $objPHPExcel->getActiveSheet()->getHeaderFooter()->setOddHeader(date('n月j日') . " " . $ghsTitle);
  172. $objPHPExcel->getActiveSheet()->getPageMargins()->setTop(0.7);
  173. $objPHPExcel->getActiveSheet()->getPageMargins()->setBottom(0.1);
  174. $objPHPExcel->getActiveSheet()->getPageMargins()->setLeft(0.5);
  175. $objPHPExcel->getActiveSheet()->getPageMargins()->setRight(0);
  176. $objPHPExcel->getActiveSheet()->getPageMargins()->setHeader(0.1);
  177. $objPHPExcel->getActiveSheet()->getPageMargins()->setFooter(0);
  178. $objPHPExcel->getActiveSheet()->setCellValue('A1', '名称');
  179. $objPHPExcel->getActiveSheet()->setCellValue('B1', '数量');
  180. $objPHPExcel->getActiveSheet()->setCellValue('C1', '金额');
  181. //设置宽度
  182. $objPHPExcel->getActiveSheet()->getColumnDimension('A')->setWidth(30);
  183. $objPHPExcel->getActiveSheet()->getColumnDimension('B')->setWidth(20);
  184. $objPHPExcel->getActiveSheet()->getColumnDimension('C')->setWidth(20);
  185. //加粗
  186. $objPHPExcel->getActiveSheet()->getStyle('A1')->getFont()->setSize(9)->setBold(true);
  187. $objPHPExcel->getActiveSheet()->getStyle('B1')->getFont()->setSize(9)->setBold(true);
  188. $objPHPExcel->getActiveSheet()->getStyle('C1')->getFont()->setSize(9)->setBold(true);
  189. $objPHPExcel->getActiveSheet()->getStyle('A1')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
  190. $objPHPExcel->getActiveSheet()->getStyle('B1')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
  191. $objPHPExcel->getActiveSheet()->getStyle('C1')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
  192. $baseRow = 2;
  193. foreach ($list as $key => $custom) {
  194. $i = $baseRow + $key;
  195. $objPHPExcel->getActiveSheet()->setCellValue('A' . $i, $custom['name']);
  196. $objPHPExcel->getActiveSheet()->setCellValue('B' . $i, $custom['num']);
  197. $objPHPExcel->getActiveSheet()->setCellValue('C' . $i, $custom['amount']);
  198. //居左
  199. $objPHPExcel->getActiveSheet()->getStyle('A' . $i)->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
  200. $objPHPExcel->getActiveSheet()->getStyle('B' . $i)->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
  201. $objPHPExcel->getActiveSheet()->getStyle('C' . $i)->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
  202. }
  203. $fileName = '采购花材-' . date("m-d");
  204. $objPHPExcel->getActiveSheet()->setTitle($fileName);
  205. $objPHPExcel->setActiveSheetIndex(0);
  206. $dir = './priceTable/' . $mainId;
  207. if (file_exists($dir) == false) {
  208. mkdir($dir, 0777, true);
  209. }
  210. $date = $fileName;
  211. $file = $date . '.xls';
  212. $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
  213. if (file_exists($dir . '/' . $file)) {
  214. unlink($dir . '/' . $file);
  215. }
  216. $objWriter->save($dir . '/' . $file);
  217. $fileUrl = Yii::$app->params['ghsHost'] . '/priceTable/' . $mainId . '/' . $file;
  218. util::success(['file' => $fileUrl, 'shortFile' => $file]);
  219. }
  220. /**
  221. * 采购人业绩:入库日按 actPrice(当天售后已减实付);隔天售后按通过日扣金额,笔数不减。
  222. * @param int $mainId
  223. * @return array{staffCg:array}
  224. */
  225. public static function statCgYj($mainId)
  226. {
  227. $get = Yii::$app->request->get();
  228. $where = [];
  229. $where['mainId'] = $mainId;
  230. $searchTime = isset($get['searchTime']) && !empty($get['searchTime']) ? $get['searchTime'] : 'today';
  231. $startTime = $get['startTime'] ?? '';
  232. $endTime = $get['endTime'] ?? '';
  233. $period = dateUtil::formatTime($searchTime, $startTime, $endTime, true);
  234. $start = $period['startTime'];
  235. $end = $period['endTime'];
  236. $where['time'] = ['between', [$start, $end]];
  237. $currentStartDate = date('Y-m-d', strtotime($start));
  238. $currentEndDate = date('Y-m-d', strtotime($end));
  239. $currentStartTime = $currentStartDate . ' 00:00:00';
  240. $currentEndTime = $currentEndDate . ' 23:59:59';
  241. $cgWhere = ['mainId' => $mainId, 'status' => 4];
  242. $cgWhere['entryTime'] = ['between', [$currentStartTime, $currentEndTime]];
  243. $cgList = PurchaseOrderClass::getAllByCondition($cgWhere, null, '*');
  244. $cgData = [];
  245. if (!empty($cgList)) {
  246. foreach ($cgList as $cgKey => $cgInfo) {
  247. $cgStaffId = $cgInfo['cgStaffId'] ?? 0;
  248. $cgStaffName = $cgInfo['cgStaffName'] ?? '';
  249. $actPrice = $cgInfo['actPrice'] ?? 0;
  250. if (isset($cgData[$cgStaffId])) {
  251. $cgData[$cgStaffId]['amount'] = bcadd($cgData[$cgStaffId]['amount'], $actPrice, 2);
  252. $cgData[$cgStaffId]['num'] = bcadd($cgData[$cgStaffId]['num'], 1);
  253. } else {
  254. $cgData[$cgStaffId]['cgStaffId'] = $cgStaffId;
  255. $cgData[$cgStaffId]['cgStaffName'] = $cgStaffName;
  256. $cgData[$cgStaffId]['amount'] = $actPrice;
  257. $cgData[$cgStaffId]['num'] = 1;
  258. }
  259. }
  260. }
  261. // 隔天售后:按原单采购人扣金额;无当日入库也建负行(笔数不减)
  262. $nextDayDeduct = CgPurchaseNextDayClass::sumAmountByMainAndTime($mainId, $currentStartTime, $currentEndTime);
  263. $nextMap = CgPurchaseNextDayClass::sumAmountGroupByCgStaff($mainId, $currentStartTime, $currentEndTime);
  264. foreach ($nextMap as $sid => $row) {
  265. $amt = $row['amount'] ?? '0';
  266. if (bccomp((string)$amt, '0', 2) <= 0) {
  267. continue;
  268. }
  269. if (!isset($cgData[$sid])) {
  270. $cgData[$sid] = [
  271. 'cgStaffId' => $sid,
  272. 'cgStaffName' => $row['cgStaffName'] !== '' ? $row['cgStaffName'] : '未记名',
  273. 'amount' => '0.00',
  274. 'num' => 0,
  275. ];
  276. }
  277. $cgData[$sid]['amount'] = bcsub((string)$cgData[$sid]['amount'], (string)$amt, 2);
  278. }
  279. if (!empty($cgData)) {
  280. // 去掉订单数与金额都为 0 的行,保留纯隔天负金额行
  281. $cgData = array_values(array_filter($cgData, function ($row) {
  282. $num = bcadd((string)($row['num'] ?? '0'), '0', 2);
  283. $amount = bcadd((string)($row['amount'] ?? '0'), '0', 2);
  284. return bccomp($num, '0', 2) != 0 || bccomp($amount, '0', 2) != 0;
  285. }));
  286. }
  287. return ['staffCg' => $cgData, 'nextDayDeduct' => $nextDayDeduct];
  288. }
  289. }