argument('month'))->startOfMonth(); $connection = (string) config('upstream_mirrors.connection', 'business_mirror'); $rowsTable = 'product_monthly_weighted_average_rows'; $now = now(); $inventory = $this->inventoryAggregate($connection, $month); $this->info('进销存(排除不看)合计 ' . count($inventory) . ' 个 仓库简称+商品编码'); DB::connection($connection)->table($rowsTable)->where('report_month', $month->toDateString())->delete(); $payload = []; $baseRows = 0; $deliveryRows = 0; $file = (string) ($this->option('file') ?? ''); if ($file !== '') { foreach ($this->readBaseFile($file) as $row) { $payload[$this->rowKey($month, $row['warehouse_short_name'], $row['product_code'])] = [ 'report_month' => $month->toDateString(), 'row_key' => $this->rowKey($month, $row['warehouse_short_name'], $row['product_code']), 'barcode_warehouse' => $row['barcode_warehouse'], 'warehouse_short_name' => $row['warehouse_short_name'], 'product_code' => $row['product_code'], 'opening_unit_price' => $row['opening_unit_price'], 'previous_pricing_status' => $row['previous_pricing_status'], 'base_source' => 'base', 'imported_at' => $now, ]; $baseRows++; } } elseif ($this->option('from-previous')) { $previous = $month->subMonthNoOverflow(); foreach (DB::connection($connection)->table($rowsTable) ->where('report_month', $previous->toDateString()) ->get(['barcode_warehouse', 'warehouse_short_name', 'product_code', 'pricing_status', 'weighted_average_unit_price']) as $row) { $key = $this->rowKey($month, $row->warehouse_short_name, $row->product_code); $payload[$key] = [ 'report_month' => $month->toDateString(), 'row_key' => $key, 'barcode_warehouse' => $row->barcode_warehouse, 'warehouse_short_name' => $row->warehouse_short_name, 'product_code' => $row->product_code, 'opening_unit_price' => $row->weighted_average_unit_price, 'previous_pricing_status' => $row->pricing_status, 'base_source' => 'base', 'imported_at' => $now, ]; $baseRows++; } } else { $this->error('请给出 --file=表1.xlsx(首月)或 --from-previous(滚动月份)。'); return self::FAILURE; } // 代发仓:没有库存,但必须出现在这张表里,采购数量取销售出库。 foreach ($inventory as $key => $item) { if (! $this->isDeliveryWarehouse($item['warehouse_short_name'])) { continue; } $rowKey = $this->rowKey($month, $item['warehouse_short_name'], $item['product_code']); if (isset($payload[$rowKey])) { continue; } $payload[$rowKey] = [ 'report_month' => $month->toDateString(), 'row_key' => $rowKey, 'barcode_warehouse' => $item['warehouse_short_name'] . $item['product_code'], 'warehouse_short_name' => $item['warehouse_short_name'], 'product_code' => $item['product_code'], 'opening_unit_price' => null, 'previous_pricing_status' => null, 'base_source' => 'delivery', 'imported_at' => $now, ]; $deliveryRows++; } $matched = 0; foreach ($payload as $key => $row) { $item = $inventory[$row['warehouse_short_name'] . "\0" . $row['product_code']] ?? null; $isDelivery = $row['base_source'] === 'delivery'; $openingQuantity = $isDelivery ? 0.0 : (float) ($item['opening_quantity'] ?? 0); $purchaseQuantity = $isDelivery ? abs((float) ($item['sales_out'] ?? 0)) : (float) ($item['purchase_in'] ?? 0); if ($item !== null) { $matched++; } $payload[$key]['product_name'] = $item['category'] ?? null; $payload[$key]['opening_quantity'] = $openingQuantity; $payload[$key]['purchase_quantity'] = $purchaseQuantity; $payload[$key]['opening_original_amount'] = $row['opening_unit_price'] === null ? null : round($openingQuantity * (float) $row['opening_unit_price'], 2); } foreach (array_chunk($payload, 1000, true) as $chunk) { DB::connection($connection)->table($rowsTable)->insert(array_values($chunk)); } DB::connection($connection)->table('product_monthly_weighted_average_states')->updateOrInsert( ['report_month' => $month->toDateString()], [ 'status' => 'base_imported', 'base_filename' => $file !== '' ? basename($file) : null, 'base_rows' => $baseRows, 'delivery_rows' => $deliveryRows, 'matched_rows' => $matched, 'last_error' => null, 'updated_at' => $now, 'created_at' => $now, ], ); $this->info(sprintf( '%s 基数导入完成:基表 %d 行 + 代发仓 %d 行,合计 %d 行,其中 %d 行在进销存总表匹配到数据。', $month->format('Y-m'), $baseRows, $deliveryRows, count($payload), $matched, )); return self::SUCCESS; } /** @return array */ private function inventoryAggregate(string $connection, CarbonImmutable $month): array { $generation = DB::connection($connection)->table('inventory_report_authoritative_months') ->where('source_month', $month->toDateString()) ->value('active_generation'); if (! $generation) { throw new \RuntimeException($month->format('Y-m') . ' 的进销存总表尚未导入。'); } $mappingTable = (new InventoryWarehouseNameMapping)->getConnection()->getDatabaseName() . '.' . DB::connection((new InventoryWarehouseNameMapping)->getConnectionName())->getTablePrefix() . (new InventoryWarehouseNameMapping)->getTable(); $sql = "select m.warehouse_short_name as short_name, r.product_code as product_code, sum(r.opening_quantity) as opening_quantity, sum(r.purchase_in) as purchase_in, sum(r.sales_out) as sales_out, max(r.category) as category from inventory_report_authoritative_rows r join {$mappingTable} m on m.warehouse_provider = r.warehouse_provider and m.warehouse = r.warehouse and m.company_id = 1 and m.deleted_at is null where r.generation = ? and m.warehouse_short_name not like '%不看%' group by m.warehouse_short_name, r.product_code"; $aggregate = []; foreach (DB::connection($connection)->select($sql, [$generation]) as $row) { $aggregate[$row->short_name . "\0" . $row->product_code] = [ 'warehouse_short_name' => (string) $row->short_name, 'product_code' => (string) $row->product_code, 'opening_quantity' => (string) $row->opening_quantity, 'purchase_in' => (string) $row->purchase_in, 'sales_out' => (string) $row->sales_out, 'category' => (string) ($row->category ?? ''), ]; } return $aggregate; } /** @return \Generator */ private function readBaseFile(string $file): \Generator { $reader = IOFactory::createReaderForFile($file); $reader->setReadDataOnly(true); $spreadsheet = $reader->load($file); $sheet = $spreadsheet->getSheetByName('7月份期初基数') ?? $spreadsheet->getSheet(0); foreach ($sheet->getRowIterator(3) as $row) { $cells = []; foreach ($row->getCellIterator('A', 'P') as $cell) { $cells[] = $cell->getFormattedValue(); } $shortName = trim((string) ($cells[2] ?? '')); $productCode = trim((string) ($cells[4] ?? '')); if ($shortName === '' || $productCode === '') { continue; } $price = trim((string) ($cells[7] ?? '')); $previousStatus = trim((string) ($cells[15] ?? '')); yield [ 'barcode_warehouse' => trim((string) ($cells[1] ?? '')) ?: $shortName . $productCode, 'warehouse_short_name' => $shortName, 'product_code' => $productCode, 'opening_unit_price' => $price === '' ? null : round((float) str_replace(',', '', $price), 2), 'previous_pricing_status' => $previousStatus === '' ? null : $previousStatus, ]; } } private function isDeliveryWarehouse(string $shortName): bool { return str_contains($shortName, '代发'); } private function rowKey(CarbonImmutable $month, string $shortName, string $productCode): string { return sha1($month->format('Y-m') . '|' . $shortName . '|' . $productCode); } }