MReceivable.Class.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. <?php
  2. /**
  3. * 财务应收生成和检测脚本
  4. * Created by PhpStorm.
  5. * User: phperstar
  6. * Date: 2019/12/19
  7. * Time: 10:38 AM
  8. */
  9. namespace Jobs\Model\MTopic\Finance;
  10. use Jobs\Dao\DShop;
  11. use Mall\Framework\Factory;
  12. use Jobs\Cache\OverviewCache;
  13. use Jobs\Dao\Purchase\DPurchase;
  14. use Jobs\Dao\Stock\DInventoryBatch;
  15. use Jobs\Dao\Stock\DWarehouse;
  16. use Mall\Framework\Core\StatusCode;
  17. use Jobs\Dao\Stock\DInventoryDetails;
  18. use Jobs\Dao\Order\DOrder;
  19. use Jobs\Dao\Stock\DInventoryIn;
  20. use Jobs\Dao\Stock\DInventoryOut;
  21. use Jobs\Dao\Finance\DReceiveReceiptIndex;
  22. use Jobs\Dao\Finance\DReceive;
  23. use Jobs\Cache\FinanceCache;
  24. class MReceivable
  25. {
  26. private $objDOrder;
  27. private $objDInventoryIn;
  28. private $objDReceive;
  29. private $objDReceiveReceiptIndex;
  30. private $objFinanceCache;
  31. private $objDInventoryDetails;
  32. private $objDWarehouse;
  33. private $enterpriseId;
  34. private $objOverviewCache;
  35. private $objDInventoryBatch;
  36. private $objDPurchase;
  37. private $objDShop;
  38. private $cutTable = 200000; // 订单表切割基数
  39. public function __construct()
  40. {
  41. $this->objDOrder = new DOrder('default');
  42. $this->objDInventoryIn = new DInventoryIn('stock');
  43. $this->objDReceive = new DReceive('finance');
  44. $this->objDReceiveReceiptIndex = new DReceiveReceiptIndex('finance');
  45. $this->objFinanceCache = new FinanceCache();
  46. $this->objDInventoryDetails = new DInventoryDetails();
  47. $this->objDWarehouse = new DWarehouse();
  48. $this->objOverviewCache = new OverviewCache();
  49. $this->objDInventoryBatch = new DInventoryBatch();
  50. $this->objDPurchase = new DPurchase();
  51. $this->objDShop = new DShop();
  52. $this->objDReceive->setSearchIndex('should_receive_receipt_search')->setType('should_receive_receipt');
  53. }
  54. /**
  55. * @param $params
  56. */
  57. public function checkExistCreate($params)
  58. {
  59. echo '检查应收单开始'.PHP_EOL;
  60. print_r($params);
  61. self::checkSaleOutCreateReceivable($params['enterpriseId']);
  62. echo '检查成功'.PHP_EOL;
  63. }
  64. /**
  65. * 检测销售出库单是否都生成应收单了
  66. */
  67. public function checkSaleOutCreateReceivable($enterpriseId)
  68. {
  69. $financeDbName = Factory::config()->get('db.finance.dbname');
  70. $stockDbName = Factory::config()->get('db.stock.dbname');
  71. $defaultDbName = Factory::config()->get('db.default.dbname');
  72. if (!$financeDbName || !$stockDbName || !$defaultDbName) {
  73. echo "dbname获取错误";
  74. return false;
  75. }
  76. //查出该企业下 审核通过但未生成应收单的销售出库单
  77. $sql = 'select a.originId,a.customerId,a.type,b.userCenterId,a.id from `'. $stockDbName .'`.`qianniao_inventory_out_'. $enterpriseId .'` as a JOIN `'. $defaultDbName .'`.qianniao_customer_'. $enterpriseId .' AS b ON a.customerId=b.id AND a.sourceNo NOT IN (select sourceNo from `'. $financeDbName .'`.`qianniao_receive_receipt_index_'. $enterpriseId .'`) and a.auditStatus = '. StatusCode::$auditStatus['auditPass'].' AND a.type ='.StatusCode::$orderType['saleOut'];
  78. $salesOutData = $this->objDOrder->query($sql);
  79. if (!empty($salesOutData)) {
  80. foreach ($salesOutData as $salesOut) {
  81. $result = self::createReceivable([
  82. 'userCenterId' => $salesOut['userCenterId'],
  83. 'enterpriseId' => $enterpriseId,
  84. 'id' => $salesOut['id'],//订单id
  85. 'type' => StatusCode::$orderType['saleOut'],
  86. 'checkOldData' => false
  87. ]);
  88. print_r('订单'.$salesOut['originId'].'生成应收'.$result);
  89. }
  90. }
  91. //查出该企业下 审核通过但未生成应收单的销售出库单
  92. $sql = 'select a.id,a.type,b.userCenterId from `'. $stockDbName .'`.`qianniao_inventory_in_'. $enterpriseId .'` as a JOIN `'. $defaultDbName .'`.qianniao_order_index_'. $enterpriseId .' AS b ON a.originId=b.id AND a.originNo NOT IN (select sourceNo from `'. $financeDbName .'`.`qianniao_receive_receipt_index_'. $enterpriseId .'`) and a.auditStatus = '. StatusCode::$auditStatus['auditPass'].' AND a.type ='.StatusCode::$orderType['saleReturnIn'];
  93. $saleReturnInData = $this->objDOrder->query($sql);
  94. if (!empty($saleReturnInData)) {
  95. foreach ($saleReturnInData as $saleReturnIn) {
  96. $result = self::createReceivable([
  97. 'userCenterId' => $saleReturnIn['userCenterId'],
  98. 'enterpriseId' => $enterpriseId,
  99. 'id' => $saleReturnIn['id'],//入库单Id
  100. 'type' => StatusCode::$orderType['saleReturnIn'],
  101. 'checkOldData' => false
  102. ]);
  103. print_r('入库单'.$saleReturnIn['id'].'生成应收'.$result);
  104. }
  105. }
  106. }
  107. /**
  108. * 销售出库单生成应收单
  109. * @param $params
  110. * @return bool
  111. * @throws \Exception
  112. */
  113. public function createReceivable($params)
  114. {
  115. // 参数校验
  116. if (empty($params)) {
  117. echo '请求创建应收单参数为空' . PHP_EOL;
  118. return false;
  119. }
  120. foreach ($params as $key => $value){
  121. if(empty($value)){
  122. echo $key.'参数错误' . PHP_EOL;
  123. return false;
  124. }
  125. }
  126. $this->enterpriseId = $params['enterpriseId'];
  127. $receiveExtraData = [];
  128. $orderData = [];
  129. $receiveData = [];
  130. //销售出库生成应收
  131. if ($params['type'] == StatusCode::$orderType['saleOut']) {
  132. // 查询销售出库单数据
  133. $objDInventoryOut = new DInventoryOut('stock');
  134. $objDInventoryOut->setTable('qianniao_inventory_out_'.$params['enterpriseId']);
  135. $inventoryOutData = $objDInventoryOut->get(['id'=>$params['id']]);
  136. if($inventoryOutData === false){
  137. echo 'sql错误'.$objDInventoryOut->error().PHP_EOL;
  138. return false;
  139. }
  140. if (empty($inventoryOutData)) {
  141. echo '销售出库单id:' . $params['id'] . '的数据为空'.PHP_EOL;
  142. return false;
  143. }
  144. // 查询订单信息
  145. $tableName = $this->objDOrder->getTableName('qianniao_order_' . $params['enterpriseId'], $params['userCenterId'], $this->cutTable);
  146. $this->objDOrder->setTable($tableName);
  147. $orderData = $this->objDOrder->get(['id' => $inventoryOutData['originId']]);
  148. if ($orderData === false) {
  149. echo 'sql错误' . $this->objDOrder->error() . PHP_EOL;
  150. return false;
  151. }
  152. if (empty($orderData)) {
  153. echo '源销售订单id:' . $inventoryOutData['originId'] . '的数据为空' . PHP_EOL;
  154. return false;
  155. }
  156. // 统计首页概况今日毛利
  157. self::getInfo($orderData);
  158. // 组装应收单数据
  159. $receiveExtraData = [
  160. 'orderId' => $orderData['id'], // 销售出库单对应的应收单订单id为销售订单id
  161. 'customerId' => $orderData['customerId'],
  162. 'customerName' => $orderData['customerName'],
  163. 'sourceNo' => $orderData['no'],
  164. 'receiveMoney' => $inventoryOutData['amount'],
  165. 'notOffsetMoney'=> $inventoryOutData['amount'],
  166. 'discountMoney' => 0,
  167. 'receiptTypeId' => StatusCode::$orderType['saleOrder'],
  168. ];
  169. // 创建应收单数据
  170. $receiveData = [
  171. 'shopId' => $orderData['shopId'],
  172. 'shopName' => $orderData['shopName'],
  173. 'financeTypeId' => StatusCode::$orderType['saleOut'],
  174. 'financeType' => '销售单',
  175. 'auditStatus' => StatusCode::$auditStatus['auditing'],
  176. 'createTime' => time(),
  177. 'updateTime' => time(),
  178. ];
  179. }
  180. //销售退货入库生成应收
  181. if ($params['type'] == StatusCode::$orderType['saleReturnIn']) {
  182. // 查询销售退货入库单信息
  183. $this->objDInventoryIn->setTable('qianniao_inventory_in_' . $params['enterpriseId']);
  184. $inventoryInData = $this->objDInventoryIn->get(['id' => $params['id']]);
  185. if ($inventoryInData === false) {
  186. echo 'sql错误' . $this->objDInventoryIn->error() . PHP_EOL;
  187. return false;
  188. }
  189. if (empty($inventoryInData)) {
  190. echo '销售退货-入库单id:' . $params['id'] . '的数据为空' . PHP_EOL;
  191. return false;
  192. }
  193. //源销售订单的信息
  194. $tableName = $this->objDOrder->getTableName('qianniao_order_' . $params['enterpriseId'], $params['userCenterId'], $this->cutTable);
  195. $this->objDOrder->setTable($tableName);
  196. $orderData = $this->objDOrder->get(['id' => $inventoryInData['originId']]);
  197. if ($orderData === false) {
  198. echo 'sql错误' . $this->objDOrder->error() . PHP_EOL;
  199. return false;
  200. }
  201. if (empty($orderData)) {
  202. echo '源销售订单id:' . $inventoryInData['originId'] . '的数据为空' . PHP_EOL;
  203. return false;
  204. }
  205. $receiveExtraData = [
  206. 'orderId' => $inventoryInData['sourceId'], // 销售退货应收订单id为销售退货单id
  207. 'sourceNo' => $inventoryInData['sourceNo'],
  208. 'customerId' => $orderData['customerId'],
  209. 'customerName' => $orderData['customerName'],
  210. 'receiveMoney' => '-' . $inventoryInData['amount'],
  211. 'discountMoney' => 0.00,
  212. 'receiptTypeId' => StatusCode::$orderType['saleReturn'],
  213. 'notOffsetMoney'=>'-' . $inventoryInData['amount'],
  214. ];
  215. // 创建应收单数据
  216. $receiveData = [
  217. 'shopId' => $orderData['shopId'],
  218. 'shopName' => $orderData['shopName'],
  219. 'financeTypeId' => StatusCode::$orderType['saleOut'],
  220. 'financeType' => '销售退款单',
  221. 'auditStatus' => StatusCode::$auditStatus['auditing'],
  222. 'createTime' => time(),
  223. 'updateTime' => time(),
  224. ];
  225. }
  226. $receiveData = array_merge($receiveData, $receiveExtraData);
  227. // 添加应收单
  228. $beginTransactionStatus = $this->objDReceive->beginTransaction();
  229. $this->objDReceive->setTable('qianniao_receive_receipt_' . $params['enterpriseId'] . '_' . date('Y') . '_' . ceil(date('m') / 3));
  230. $dbResult = $this->objDReceive->get('createTime >='.strtotime(date('Ymd'.'0:0:0')), 'no', 'createTime desc');
  231. if ($dbResult === false) {
  232. echo 'sql错误' . $this->objDReceive->error() . PHP_EOL;
  233. return false;
  234. }
  235. if(empty($dbResult)){
  236. $receiveData['no'] = createSerialNumberByDate('');
  237. }else{
  238. $receiveData['no'] = createSerialNumberByDate($dbResult['no']);
  239. }
  240. //索引表数据
  241. $indexData = [
  242. 'receiveReceiptId' => 0,
  243. 'customerId' => $receiveData['customerId'],
  244. 'sourceNo' => $receiveData['sourceNo'],
  245. 'auditStatus' => $receiveData['auditStatus'],
  246. 'financeTypeId' => $receiveData['financeTypeId'],
  247. 'financeType' => $receiveData['financeType'],
  248. 'shopId' => $receiveData['shopId'],
  249. 'createTime' => $receiveData['createTime'],
  250. 'updateTime' => $receiveData['updateTime'],
  251. ];
  252. $this->objDReceiveReceiptIndex->setTable('qianniao_receive_receipt_index_' . $params['enterpriseId']);
  253. $receiveReceiptId = $this->objDReceiveReceiptIndex->insert($indexData);
  254. if ($receiveReceiptId === false) {
  255. $this->objDReceive->rollBack();
  256. echo 'sql错误' . $this->objDReceiveReceiptIndex->error() . PHP_EOL;
  257. return false;
  258. }
  259. //判断收款金额是否为负数,核销状态直接给5
  260. $receiveData['offsetStatus'] = ($receiveData['receiveMoney']<0) ? 5 : 4;
  261. $receiveData['id'] = $receiveReceiptId;
  262. $dbResult = $this->objDReceive->insert($receiveData);
  263. if ($dbResult === false) {
  264. $this->objDReceive->rollBack();
  265. echo 'sql错误' . $this->objDReceive->error() . PHP_EOL;
  266. return false;
  267. }
  268. if($beginTransactionStatus){
  269. $this->objDReceive->commit();
  270. }
  271. echo '数据库添加应收单成功' . PHP_EOL;
  272. /*
  273. $_id = self::createEsDocumentId($ReceiveId, $params['enterpriseId']);
  274. $esData = $receiveData;
  275. $esData['id'] = $ReceiveId;
  276. $esData['enterpriseId'] = $params['enterpriseId'];
  277. $result = $this->objDReceive->addUpSearchIndexDocument($esData, $_id);
  278. if (isset($result['_shards']) && isset($result['_shards']['successful']) && $result['_shards']['successful'] == 1) {
  279. echo "es操作成功";//die;
  280. }else{
  281. echo "es操作失败";
  282. file_put_contents('/apps/logs/elasticsearch.log',date('Y-m-d H:i:s').'生成应收es错误,错误原因'.var_export($result,true).PHP_EOL,FILE_APPEND);
  283. }
  284. echo 'es添加应收单成功' . PHP_EOL;*/
  285. // 添加应收队列缓存,计划任务自动审核应收用
  286. $this->objFinanceCache->saveSalesOutReceive($params['enterpriseId'], $receiveReceiptId, $receiveData['createTime']);
  287. if(!isset($params['checkOldData'])) {
  288. echo '检测销售出库单是否都生成应收单了';
  289. self::checkSaleOutCreateReceivable($params['enterpriseId']);
  290. }
  291. return true;
  292. }
  293. private function createEsDocumentId($receiveId, $enterpriseId)
  294. {
  295. $t = date('Y') . '_' . ceil(date('m') / 3);
  296. return 'EnterpriseId_' . $enterpriseId . '_' . $t . '_receiveId_' . $receiveId;
  297. }
  298. /**
  299. * @param $orderData
  300. * @throws \Exception
  301. */
  302. private function getInfo($orderData)
  303. {
  304. // 查询订单对应店铺绑定的仓库id
  305. $this->objDShop->setTable('qianniao_shop_1');
  306. $warehouseId = $this->objDShop->get_field('warehouseId',['id'=>$orderData['shopId']]);
  307. // 获取当前订单的shopId对应的仓库id
  308. $this->objDWarehouse->setTable('qianniao_warehouse_' . $this->enterpriseId);
  309. if ($warehouseId === false || empty($warehouseId)) {
  310. echo '商铺id'. $orderData['shopId'] .'获取仓库id时出错' . $this->objDWarehouse->error() . PHP_EOL;
  311. return false;
  312. }
  313. $warehouseId = strpos($warehouseId,',') ? explode(',', $warehouseId) : [$warehouseId];
  314. $orderData['warehouseId'] = $warehouseId;
  315. // 查询当前订单的流水记录
  316. $this->objDPurchase->setTable('qianniao_purchase_' . $this->enterpriseId);//切换分表
  317. $inventoryDetails = [];
  318. foreach($warehouseId as $wid){
  319. self::setDetailsTable($this->enterpriseId, $wid, 'qianniao_inventory_details');//切换分表
  320. self::setBatchTable($this->enterpriseId, $wid, 'qianniao_inventory_batch');//切换分表
  321. $dbResult = $this->objDInventoryDetails->select(['actionType' => StatusCode::$delete, 'originId' => $orderData['id']]);
  322. if ($dbResult === false) {
  323. echo '库存流水查询出错' . $this->objDInventoryDetails->error() . PHP_EOL;
  324. return false;
  325. }
  326. if(!empty($dbResult)){
  327. $inventoryDetails = array_merge($inventoryDetails, $dbResult);
  328. }
  329. }
  330. if (empty($inventoryDetails)) {
  331. echo '库存流水数据为空' . PHP_EOL;
  332. return false;
  333. }
  334. self::todayGrossProfit($inventoryDetails, $orderData);
  335. //self::supplierRanking($inventoryDetails, $orderData);
  336. }
  337. /**
  338. * 缓存订单毛利
  339. * @param $inventoryDetails
  340. * @param $orderData
  341. */
  342. private function todayGrossProfit($inventoryDetails, $orderData)
  343. {
  344. $averageCostTotal = 0;
  345. $batchCostTotal = 0;
  346. foreach ($inventoryDetails as $detail) {
  347. switch ($detail['costType']) {
  348. case StatusCode::$costType['mwa']://平均值
  349. $averageCostTotal += bcmul($detail['inventoryNum'], $detail['averageCost'], 2);
  350. break;
  351. case StatusCode::$costType['sp']://批次成本
  352. $batch = json_decode($detail['batch'], true);
  353. foreach ($batch as $val) {
  354. $batchCostTotal += bcmul($val['num'], $val['batchCost'], 2);
  355. }
  356. break;
  357. }
  358. }
  359. //TODO(这里暂时不回写订单商品的出库成本)
  360. $costTotal = bcadd($averageCostTotal, $batchCostTotal, 2);//此订单的总成本
  361. $grossProfit = bcsub($orderData['payAmount'], $costTotal, 2);
  362. $this->objOverviewCache->saveBusinessOverview($this->enterpriseId, 'todayGrossProfit', $grossProfit);//订单毛利
  363. $this->objOverviewCache->saveBusinessOverview($this->enterpriseId, 'todayGrossProfit', $grossProfit, $orderData['shopId']);
  364. echo '订单' . $orderData['no'] . ':毛利缓存成功' . PHP_EOL;
  365. }
  366. /**
  367. * @param $inventoryDetails
  368. * @param $orderData
  369. */
  370. private function supplierRanking($inventoryDetails, $orderData)
  371. {
  372. $batchNo = [];
  373. foreach ($inventoryDetails as $detail) {
  374. $batch = json_decode($detail['batch'], true);
  375. foreach ($batch as $val) {
  376. $batchNo[] = $val['batch'];
  377. }
  378. }
  379. $fields = 'p.supplierId,p.supplierName,p.warehouseId,b.materielId,b.skuId,b.batchNo';
  380. $sql = 'SELECT ' . $fields . ' FROM ' . $this->objDInventoryBatch->get_Table() . ' AS b LEFT JOIN ' . $this->objDPurchase->get_Table() . ' AS p ON b.originId=p.id WHERE b.batchNo IN(' . implode(',', $batchNo) . ')';
  381. $supplier = $this->objDInventoryBatch->query($sql);
  382. if ($supplier === false) {
  383. echo '查询订单商品所属供应商时出错' . $this->objDInventoryBatch->error() . PHP_EOL;
  384. die;
  385. }
  386. if (empty($supplier)) {
  387. echo '查询订单商品对应供应商数据为空' . PHP_EOL;
  388. die;
  389. }
  390. $allSupplier = [];
  391. foreach ($supplier as $val) {
  392. $allSupplier[$val['batchNo']] = $val;
  393. }
  394. foreach ($inventoryDetails as &$detail) {
  395. $batch = json_decode($detail['batch'], true);
  396. foreach ($batch as &$val) {
  397. $val['supplierId'] = isset($allSupplier[$val['batch']]['supplierId']) ? $allSupplier[$val['batch']]['supplierId'] : 0;
  398. $this->objOverviewCache->saveRanking($this->enterpriseId, 'supplierRanking', $val['supplierId'], $val['num']);//销量
  399. $this->objOverviewCache->saveRanking($this->enterpriseId, 'supplierRanking', $val['supplierId'], $val['num'], $orderData['shopId']);
  400. $this->objOverviewCache->saveSalesMoneyRanking($this->enterpriseId, 'supplierRanking', $val['supplierId'], $orderData['payAmount']);//销额
  401. $this->objOverviewCache->saveSalesMoneyRanking($this->enterpriseId, 'supplierRanking', $val['supplierId'], $orderData['payAmount'], $orderData['shopId']);
  402. }
  403. }
  404. echo '出库供应商数据统计成功' . PHP_EOL;
  405. }
  406. /**
  407. * 库存批次按照企业商铺分表
  408. * @param $enterpriseId
  409. * @param $warehouseId
  410. * @param $tableName
  411. * @throws \Exception
  412. */
  413. public function setBatchTable($enterpriseId, $warehouseId, $tableName)
  414. {
  415. $tableName = $tableName . '_' . $enterpriseId . '_' . $warehouseId;
  416. $this->objDInventoryBatch->setTable($tableName);
  417. }
  418. /**
  419. * 库存流水按照仓库+季度分表
  420. * @param $enterpriseId
  421. * @param $warehouseId
  422. * @param $tableName
  423. * @throws \Exception
  424. */
  425. private function setDetailsTable($enterpriseId, $warehouseId, $tableName)
  426. {
  427. $tableName = $tableName . '_' . $enterpriseId . '_' . $warehouseId . '_' . substr(date('Y'), -2) . ceil(date('m') / 3);
  428. $this->objDInventoryDetails->setTable($tableName);
  429. }
  430. }