StatCgClass.php 16 KB

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