在「第三方仓储报价」页新增仓储费核算:选月份一键按当期生效报价核算 (入库管理费/出库费用/每日仓储费),结果存档并可导出汇总/每日/逐商品三表。 - 新增计算服务 ThirdPartyWarehouseStorageFeeCalculator(口径与报价页公式一致, 重量取普通商品资料 ops_jst_sku.weight) - 新增存档表 business_third_party_warehouse_storage_settlements + 模型 - 新增 maatwebsite/excel 三表导出 ThirdPartyWarehouseStorageSettlementExport - 报价仓库新增 effectiveFor(按仓库+结算月取当期生效报价) - Livewire 组件与报价页新增核算/导出/历史 UI - 新增计算服务测试(附 2026-08 真实验收基准 1538.20/6099.60/8376.48/16014.28) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DVQ6NsLsbtxm5Ng7Eew3s9
90 lines
2.8 KiB
PHP
90 lines
2.8 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Business;
|
||
|
||
use Illuminate\Database\ConnectionInterface;
|
||
use Illuminate\Database\DatabaseManager;
|
||
use Illuminate\Support\Facades\Schema;
|
||
use Throwable;
|
||
|
||
/**
|
||
* 三方仓核算的商品重量来源。
|
||
*
|
||
* 重量取【普通商品资料】主档「重量(kg)」,即业务镜像表 ops_jst_sku 的
|
||
* weight 列,按商品编码(sku_id)关联。字段与表定义与
|
||
* Modules\EcommerceFinance\Repositories\ProductCatalogMirrorRepository 一致
|
||
* (dataset operations_replica.rpt.jst_sku → ops_jst_sku,见 config/upstream_mirrors.php)。
|
||
* 同一编码可能有多条历史行,取最新 autoid 的重量。
|
||
*/
|
||
class ThirdPartyWarehouseProductWeightRepository
|
||
{
|
||
/** 普通商品资料主档在业务镜像中的落地表。 */
|
||
private const PRODUCT_TABLE = 'ops_jst_sku';
|
||
|
||
private readonly ConnectionInterface $connection;
|
||
|
||
public function __construct(DatabaseManager $databases)
|
||
{
|
||
$this->connection = $databases->connection(
|
||
(string) config('upstream_mirrors.connection', 'business_mirror'),
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 按商品编码批量取重量(kg)。
|
||
*
|
||
* @param list<string> $skuCodes
|
||
* @return array<string,float> 商品编码 => 重量(kg)
|
||
*/
|
||
public function weightsBySkuCodes(array $skuCodes): array
|
||
{
|
||
$codes = [];
|
||
foreach ($skuCodes as $code) {
|
||
$code = trim((string) $code);
|
||
if ($code !== '') {
|
||
$codes[$code] = $code;
|
||
}
|
||
}
|
||
$codes = array_values($codes);
|
||
|
||
if ($codes === [] || ! $this->available()) {
|
||
return [];
|
||
}
|
||
|
||
try {
|
||
$latest = $this->connection->table(self::PRODUCT_TABLE)
|
||
->where('sku_type', 'normal')
|
||
->whereIn('sku_id', $codes)
|
||
->groupBy('sku_id')
|
||
->select('sku_id')
|
||
->selectRaw('MAX(autoid) AS autoid');
|
||
|
||
return $this->connection->table(self::PRODUCT_TABLE . ' as s')
|
||
->joinSub($latest, 'latest', function ($join): void {
|
||
$join->on('latest.sku_id', '=', 's.sku_id')
|
||
->on('latest.autoid', '=', 's.autoid');
|
||
})
|
||
->pluck('s.weight', 's.sku_id')
|
||
->map(static fn (mixed $weight): float => (float) $weight)
|
||
->all();
|
||
} catch (Throwable) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
private function available(): bool
|
||
{
|
||
try {
|
||
$schema = Schema::connection($this->connection->getName());
|
||
|
||
return $schema->hasTable(self::PRODUCT_TABLE)
|
||
&& $schema->hasColumn(self::PRODUCT_TABLE, 'weight')
|
||
&& $schema->hasColumn(self::PRODUCT_TABLE, 'sku_id');
|
||
} catch (Throwable) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|