|
|
@@ -3,7 +3,9 @@
|
|
|
namespace bizGhs\clear\classes;
|
|
|
|
|
|
use bizGhs\base\classes\BaseClass;
|
|
|
+use bizGhs\clear\models\Clear;
|
|
|
use bizGhs\order\classes\OrderClass;
|
|
|
+use bizGhs\order\classes\PurchaseOrderClass;
|
|
|
use bizHd\purchase\classes\PurchaseClass;
|
|
|
use bizHd\purchase\classes\PurchaseClearClass;
|
|
|
use common\components\dict;
|
|
|
@@ -98,6 +100,81 @@ class OrderCgClearClass extends BaseClass
|
|
|
return $pairs;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * gys2KmGys:purchaseIds 为 xhGhsCgOrder,cgId 存基地采购单 id,orderId=0
|
|
|
+ */
|
|
|
+ public static function buildPairsFromGhsCgOrders($cgList)
|
|
|
+ {
|
|
|
+ if (empty($cgList)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ $pairs = [];
|
|
|
+ foreach ($cgList as $cg) {
|
|
|
+ $cgId = intval(is_array($cg) ? ($cg['id'] ?? 0) : ($cg->id ?? 0));
|
|
|
+ if ($cgId <= 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $cgSn = is_array($cg) ? ($cg['orderSn'] ?? '') : ($cg->orderSn ?? '');
|
|
|
+ $pairs[] = [
|
|
|
+ 'orderId' => 0,
|
|
|
+ 'orderSn' => '',
|
|
|
+ 'cgId' => $cgId,
|
|
|
+ 'cgSn' => $cgSn,
|
|
|
+ 'amount' => self::resolveDebtAmount($cg),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return $pairs;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function getRelationClearStyles()
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ intval(dict::getDict('clearStyle', 'hd2Gys')),
|
|
|
+ intval(dict::getDict('clearStyle', 'gys2Hd')),
|
|
|
+ intval(dict::getDict('clearStyle', 'gys2KmGys')),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function isRelationClearStyle($clearStyle)
|
|
|
+ {
|
|
|
+ return in_array(intval($clearStyle), self::getRelationClearStyles(), true);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function buildPairsFromClear($clear)
|
|
|
+ {
|
|
|
+ $clearStyle = intval(is_object($clear) ? ($clear->clearStyle ?? 0) : ($clear['clearStyle'] ?? 0));
|
|
|
+ if (!self::isRelationClearStyle($clearStyle)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ $saleIds = is_object($clear) ? ($clear->saleIds ?? '') : ($clear['saleIds'] ?? '');
|
|
|
+ $purchaseIds = is_object($clear) ? ($clear->purchaseIds ?? '') : ($clear['purchaseIds'] ?? '');
|
|
|
+ $gys2KmGys = intval(dict::getDict('clearStyle', 'gys2KmGys'));
|
|
|
+ if ($clearStyle === $gys2KmGys) {
|
|
|
+ $cgArr = self::parseIds($purchaseIds);
|
|
|
+ if (empty($cgArr)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ $cgList = PurchaseOrderClass::getAllByCondition(['id' => ['in', $cgArr]], null, '*', null, true);
|
|
|
+ return self::buildPairsFromGhsCgOrders($cgList);
|
|
|
+ }
|
|
|
+ $saleArr = self::parseIds($saleIds);
|
|
|
+ if (!empty($saleArr)) {
|
|
|
+ $orderList = OrderClass::getByIds($saleArr);
|
|
|
+ if (!empty($orderList)) {
|
|
|
+ return self::buildPairsFromOrders($orderList);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $cgArr = self::parseIds($purchaseIds);
|
|
|
+ if (empty($cgArr)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ $purchaseList = PurchaseClass::getAllByCondition(['id' => ['in', $cgArr]], null, '*', null, true);
|
|
|
+ if (empty($purchaseList)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ return self::buildPairsFromPurchases($purchaseList);
|
|
|
+ }
|
|
|
+
|
|
|
public static function bindRelations($clear, $pairs, $status = self::STATUS_AWAIT_PAY)
|
|
|
{
|
|
|
$clearId = is_object($clear) ? ($clear->id ?? 0) : ($clear['id'] ?? 0);
|
|
|
@@ -237,28 +314,14 @@ class OrderCgClearClass extends BaseClass
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
|
- $clearStyle = is_object($clear) ? ($clear->clearStyle ?? 0) : ($clear['clearStyle'] ?? 0);
|
|
|
- $hd2Gys = dict::getDict('clearStyle', 'hd2Gys');
|
|
|
- $gys2Hd = dict::getDict('clearStyle', 'gys2Hd');
|
|
|
- if (!in_array(intval($clearStyle), [intval($hd2Gys), intval($gys2Hd)])) {
|
|
|
+ if (!self::isRelationClearStyle(is_object($clear) ? ($clear->clearStyle ?? 0) : ($clear['clearStyle'] ?? 0))) {
|
|
|
return 0;
|
|
|
}
|
|
|
- $saleIds = is_object($clear) ? ($clear->saleIds ?? '') : ($clear['saleIds'] ?? '');
|
|
|
- $purchaseIds = is_object($clear) ? ($clear->purchaseIds ?? '') : ($clear['purchaseIds'] ?? '');
|
|
|
- $pairs = [];
|
|
|
- $saleArr = self::parseIds($saleIds);
|
|
|
- if (!empty($saleArr)) {
|
|
|
- $orderList = OrderClass::getByIds($saleArr);
|
|
|
- if (!empty($orderList)) {
|
|
|
- $pairs = self::buildPairsFromOrders($orderList);
|
|
|
- }
|
|
|
- } elseif (!empty(self::parseIds($purchaseIds))) {
|
|
|
- $cgArr = self::parseIds($purchaseIds);
|
|
|
- $purchaseList = PurchaseClass::getAllByCondition(['id' => ['in', $cgArr]], null, '*', null, true);
|
|
|
- if (!empty($purchaseList)) {
|
|
|
- $pairs = self::buildPairsFromPurchases($purchaseList);
|
|
|
- }
|
|
|
+ $clearStyle = intval(is_object($clear) ? ($clear->clearStyle ?? 0) : ($clear['clearStyle'] ?? 0));
|
|
|
+ if ($clearStyle === intval(dict::getDict('clearStyle', 'gys2KmGys'))) {
|
|
|
+ self::migrateLegacyGys2KmGysRowsByClearId($clearId, false);
|
|
|
}
|
|
|
+ $pairs = self::buildPairsFromClear($clear);
|
|
|
if (empty($pairs)) {
|
|
|
return 0;
|
|
|
}
|
|
|
@@ -270,6 +333,65 @@ class OrderCgClearClass extends BaseClass
|
|
|
return count($pairs);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 从 xhClear 估算应有关系行数(saleIds 优先,否则 purchaseIds 个数)
|
|
|
+ */
|
|
|
+ public static function expectedRelationCountFromClear($clear)
|
|
|
+ {
|
|
|
+ $saleIds = is_object($clear) ? ($clear->saleIds ?? '') : ($clear['saleIds'] ?? '');
|
|
|
+ $purchaseIds = is_object($clear) ? ($clear->purchaseIds ?? '') : ($clear['purchaseIds'] ?? '');
|
|
|
+ $saleArr = self::parseIds($saleIds);
|
|
|
+ if (!empty($saleArr)) {
|
|
|
+ return count($saleArr);
|
|
|
+ }
|
|
|
+ return count(self::parseIds($purchaseIds));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param int[] $clearIds
|
|
|
+ * @return array<int,int> clearId => 关系行数
|
|
|
+ */
|
|
|
+ public static function getRelationCountMap(array $clearIds)
|
|
|
+ {
|
|
|
+ $clearIds = array_values(array_unique(array_filter(array_map('intval', $clearIds))));
|
|
|
+ if (empty($clearIds)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ $table = self::$baseFile::tableName();
|
|
|
+ $rows = Yii::$app->db->createCommand(
|
|
|
+ 'SELECT clearId, COUNT(*) AS cnt FROM ' . $table
|
|
|
+ . ' WHERE clearId IN (' . implode(',', $clearIds) . ')'
|
|
|
+ . ' GROUP BY clearId'
|
|
|
+ )->queryAll();
|
|
|
+ $map = [];
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $map[intval($row['clearId'])] = intval($row['cnt']);
|
|
|
+ }
|
|
|
+ return $map;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否可跳过回填:关系行数已满、金额非空、无 gys2KmGys 旧格式
|
|
|
+ */
|
|
|
+ public static function shouldSkipBackfill($clear, $relationCount, array $emptyAmountClearIds, array $legacyGys2KmGysClearIds)
|
|
|
+ {
|
|
|
+ $clearId = intval(is_object($clear) ? ($clear->id ?? 0) : ($clear['id'] ?? 0));
|
|
|
+ if ($clearId <= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (in_array($clearId, $legacyGys2KmGysClearIds, true)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (in_array($clearId, $emptyAmountClearIds, true)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $expected = self::expectedRelationCountFromClear($clear);
|
|
|
+ if ($expected <= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return $relationCount >= $expected;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 批量查询关系表已存在的 clearId(避免逐条 SELECT)
|
|
|
* @param int[] $clearIds
|
|
|
@@ -311,6 +433,212 @@ class OrderCgClearClass extends BaseClass
|
|
|
return array_values(array_unique(array_map('intval', $rows)));
|
|
|
}
|
|
|
|
|
|
+ const LEGACY_UNIQUE_INDEX = 'order_id_clear_id';
|
|
|
+ const RELATION_UNIQUE_INDEX = 'uk_clear_cg_order';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * gys2KmGys 旧数据:orderId 误存基地采购单 id、cgId=0 的 clearId
|
|
|
+ * @param int[] $clearIds
|
|
|
+ * @return int[]
|
|
|
+ */
|
|
|
+ public static function getClearIdsWithLegacyGys2KmGysRows(array $clearIds)
|
|
|
+ {
|
|
|
+ $clearIds = array_values(array_unique(array_filter(array_map('intval', $clearIds))));
|
|
|
+ if (empty($clearIds)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ $gys2KmGys = intval(dict::getDict('clearStyle', 'gys2KmGys'));
|
|
|
+ $relTable = self::$baseFile::tableName();
|
|
|
+ $clearTable = Clear::tableName();
|
|
|
+ $rows = Yii::$app->db->createCommand(
|
|
|
+ 'SELECT DISTINCT r.clearId FROM ' . $relTable . ' r'
|
|
|
+ . ' INNER JOIN ' . $clearTable . ' c ON c.id = r.clearId AND c.clearStyle = :style'
|
|
|
+ . ' WHERE r.clearId IN (' . implode(',', $clearIds) . ')'
|
|
|
+ . ' AND r.cgId = 0 AND r.orderId > 0'
|
|
|
+ )->bindValue(':style', $gys2KmGys)->queryColumn();
|
|
|
+ if (empty($rows)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ return array_values(array_unique(array_map('intval', $rows)));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将 gys2KmGys 关系行从旧格式(orderId=基地采购单id,cgId=0)迁到新格式(cgId=基地采购单id,orderId=0)
|
|
|
+ * 需在删除旧唯一索引 (clearId,orderId) 之后执行,或首次迁移时由 rebuildRelationUniqueIndex 串联
|
|
|
+ */
|
|
|
+ public static function migrateLegacyGys2KmGysRows($dryRun = false, $batchSize = 500, $sleepMs = 50)
|
|
|
+ {
|
|
|
+ $gys2KmGys = intval(dict::getDict('clearStyle', 'gys2KmGys'));
|
|
|
+ $relTable = self::$baseFile::tableName();
|
|
|
+ $clearTable = Clear::tableName();
|
|
|
+ $migrated = 0;
|
|
|
+ $lastId = 0;
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ $rows = Yii::$app->db->createCommand(
|
|
|
+ 'SELECT r.id, r.clearId, r.orderId, r.orderSn, r.cgSn FROM ' . $relTable . ' r'
|
|
|
+ . ' INNER JOIN ' . $clearTable . ' c ON c.id = r.clearId AND c.clearStyle = :style'
|
|
|
+ . ' WHERE r.id > :lastId AND r.cgId = 0 AND r.orderId > 0'
|
|
|
+ . ' ORDER BY r.id ASC LIMIT ' . intval($batchSize)
|
|
|
+ )->bindValues([
|
|
|
+ ':style' => $gys2KmGys,
|
|
|
+ ':lastId' => $lastId,
|
|
|
+ ])->queryAll();
|
|
|
+
|
|
|
+ if (empty($rows)) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $lastId = intval($row['id']);
|
|
|
+ $relId = $lastId;
|
|
|
+ $clearId = intval($row['clearId']);
|
|
|
+ $cgId = intval($row['orderId']);
|
|
|
+ $cgSn = trim($row['cgSn'] ?? '');
|
|
|
+ if ($cgSn === '') {
|
|
|
+ $cgSn = trim($row['orderSn'] ?? '');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$dryRun) {
|
|
|
+ $target = self::getByCondition([
|
|
|
+ 'clearId' => $clearId,
|
|
|
+ 'orderId' => 0,
|
|
|
+ 'cgId' => $cgId,
|
|
|
+ ], true);
|
|
|
+ if (!empty($target)) {
|
|
|
+ Yii::$app->db->createCommand()->delete($relTable, ['id' => $relId])->execute();
|
|
|
+ } else {
|
|
|
+ Yii::$app->db->createCommand()->update($relTable, [
|
|
|
+ 'cgId' => $cgId,
|
|
|
+ 'cgSn' => $cgSn,
|
|
|
+ 'orderId' => 0,
|
|
|
+ 'orderSn' => '',
|
|
|
+ ], ['id' => $relId])->execute();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $migrated++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($sleepMs > 0 && !$dryRun) {
|
|
|
+ usleep($sleepMs * 1000);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $migrated;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 迁移单个 gys2KmGys 结账单下的旧格式关系行
|
|
|
+ */
|
|
|
+ public static function migrateLegacyGys2KmGysRowsByClearId($clearId, $dryRun = false)
|
|
|
+ {
|
|
|
+ $clearId = intval($clearId);
|
|
|
+ if ($clearId <= 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ $gys2KmGys = intval(dict::getDict('clearStyle', 'gys2KmGys'));
|
|
|
+ $clear = Clear::find()->where(['id' => $clearId, 'clearStyle' => $gys2KmGys])->one();
|
|
|
+ if (empty($clear)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ $relTable = self::$baseFile::tableName();
|
|
|
+ $rows = Yii::$app->db->createCommand(
|
|
|
+ 'SELECT id, clearId, orderId, orderSn, cgSn FROM ' . $relTable
|
|
|
+ . ' WHERE clearId = :clearId AND cgId = 0 AND orderId > 0'
|
|
|
+ )->bindValue(':clearId', $clearId)->queryAll();
|
|
|
+ if (empty($rows)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ $migrated = 0;
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $relId = intval($row['id']);
|
|
|
+ $cgId = intval($row['orderId']);
|
|
|
+ $cgSn = trim($row['cgSn'] ?? '');
|
|
|
+ if ($cgSn === '') {
|
|
|
+ $cgSn = trim($row['orderSn'] ?? '');
|
|
|
+ }
|
|
|
+ if (!$dryRun) {
|
|
|
+ $target = self::getByCondition([
|
|
|
+ 'clearId' => $clearId,
|
|
|
+ 'orderId' => 0,
|
|
|
+ 'cgId' => $cgId,
|
|
|
+ ], true);
|
|
|
+ if (!empty($target)) {
|
|
|
+ Yii::$app->db->createCommand()->delete($relTable, ['id' => $relId])->execute();
|
|
|
+ } else {
|
|
|
+ Yii::$app->db->createCommand()->update($relTable, [
|
|
|
+ 'cgId' => $cgId,
|
|
|
+ 'cgSn' => $cgSn,
|
|
|
+ 'orderId' => 0,
|
|
|
+ 'orderSn' => '',
|
|
|
+ ], ['id' => $relId])->execute();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $migrated++;
|
|
|
+ }
|
|
|
+ return $migrated;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重建关系表唯一索引:(clearId,cgId,orderId)
|
|
|
+ * 顺序:删旧索引 -> 迁 gys2KmGys 旧行 -> 建新索引
|
|
|
+ */
|
|
|
+ public static function rebuildRelationUniqueIndex($dryRun = false)
|
|
|
+ {
|
|
|
+ $table = self::$baseFile::tableName();
|
|
|
+ $indexMap = self::getTableIndexMap($table);
|
|
|
+ $hasLegacy = isset($indexMap[self::LEGACY_UNIQUE_INDEX]);
|
|
|
+ $hasNew = isset($indexMap[self::RELATION_UNIQUE_INDEX]);
|
|
|
+ $legacyRows = intval(Yii::$app->db->createCommand(
|
|
|
+ 'SELECT COUNT(*) FROM ' . $table . ' r'
|
|
|
+ . ' INNER JOIN ' . Clear::tableName() . ' c ON c.id = r.clearId AND c.clearStyle = :style'
|
|
|
+ . ' WHERE r.cgId = 0 AND r.orderId > 0'
|
|
|
+ )->bindValue(':style', intval(dict::getDict('clearStyle', 'gys2KmGys')))->queryScalar());
|
|
|
+
|
|
|
+ if ($dryRun) {
|
|
|
+ return [
|
|
|
+ 'hasLegacyIndex' => $hasLegacy,
|
|
|
+ 'hasNewIndex' => $hasNew,
|
|
|
+ 'legacyRows' => $legacyRows,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($hasLegacy) {
|
|
|
+ Yii::$app->db->createCommand(
|
|
|
+ 'ALTER TABLE ' . $table . ' DROP INDEX ' . self::LEGACY_UNIQUE_INDEX
|
|
|
+ )->execute();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($legacyRows > 0) {
|
|
|
+ self::migrateLegacyGys2KmGysRows(false, 500, 50);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!$hasNew) {
|
|
|
+ Yii::$app->db->createCommand(
|
|
|
+ 'ALTER TABLE ' . $table . ' ADD UNIQUE KEY ' . self::RELATION_UNIQUE_INDEX . ' (clearId, cgId, orderId)'
|
|
|
+ )->execute();
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'droppedLegacyIndex' => $hasLegacy,
|
|
|
+ 'addedNewIndex' => !$hasNew,
|
|
|
+ 'migratedLegacyRows' => $legacyRows,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static function getTableIndexMap($table)
|
|
|
+ {
|
|
|
+ $rows = Yii::$app->db->createCommand('SHOW INDEX FROM ' . $table)->queryAll();
|
|
|
+ $map = [];
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $name = $row['Key_name'] ?? '';
|
|
|
+ if ($name !== '') {
|
|
|
+ $map[$name] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $map;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 关系行 amount 取订单/采购单 actPrice
|
|
|
*/
|