93 lines
3.8 KiB
PHP
93 lines
3.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\common\service\pharmacy;
|
|
|
|
use RuntimeException;
|
|
|
|
final class EjMedicineCatalogSyncPolicy
|
|
{
|
|
/** @return array{items:array<int,array<string,mixed>>,next_cursor:int,has_more:bool} */
|
|
public static function parsePage(array $response, int $cursor): array
|
|
{
|
|
$body = is_array($response['body'] ?? null) ? $response['body'] : [];
|
|
$httpStatus = (int) ($response['http_status'] ?? 0);
|
|
if ($httpStatus < 200 || $httpStatus >= 300 || (int) ($body['code'] ?? -1) !== 0) {
|
|
$message = trim((string) ($body['message'] ?? ''));
|
|
throw new RuntimeException($message !== '' ? $message : '洛阳药房药材目录同步失败');
|
|
}
|
|
|
|
$data = is_array($body['data'] ?? null) ? $body['data'] : [];
|
|
$items = is_array($data['items'] ?? null) ? array_values(array_filter(
|
|
$data['items'],
|
|
static fn ($item): bool => is_array($item)
|
|
)) : [];
|
|
$nextCursor = max(0, (int) ($data['next_cursor'] ?? $cursor));
|
|
$hasMore = !empty($data['has_more']);
|
|
if ($hasMore && $nextCursor <= $cursor) {
|
|
throw new RuntimeException('洛阳药房药材目录游标未推进,已停止同步');
|
|
}
|
|
|
|
return ['items' => $items, 'next_cursor' => $nextCursor, 'has_more' => $hasMore];
|
|
}
|
|
|
|
/**
|
|
* @param array<string,mixed>|null $existing
|
|
* @param array<string,mixed> $remote
|
|
* @return array{action:string,values:array<string,mixed>,deactivated:int}
|
|
*/
|
|
public static function merge(?array $existing, array $remote): array
|
|
{
|
|
$code = trim((string) ($remote['medicine_code'] ?? ''));
|
|
if ($code === '') {
|
|
throw new RuntimeException('洛阳药房药材目录包含空 medicine_code');
|
|
}
|
|
|
|
$deleted = !empty($remote['deleted']) || !empty($remote['remote_deleted']);
|
|
$values = [
|
|
'medicine_code' => $code,
|
|
'name' => trim((string) ($remote['name'] ?? '')),
|
|
'brand' => trim((string) ($remote['brand'] ?? '')),
|
|
'unit' => trim((string) ($remote['unit'] ?? '')),
|
|
'settlement_price' => self::decimal($remote['settlement_price'] ?? 0),
|
|
'retail_price' => self::decimal($remote['retail_price'] ?? 0),
|
|
'status' => $deleted ? 0 : (int) ($remote['status'] ?? 0),
|
|
'catalog_version' => max(0, (int) ($remote['catalog_version'] ?? 0)),
|
|
'remote_deleted' => $deleted ? 1 : 0,
|
|
];
|
|
|
|
if ($existing === null) {
|
|
return [
|
|
'action' => 'created',
|
|
'values' => $values,
|
|
'deactivated' => $values['status'] === 0 ? 1 : 0,
|
|
];
|
|
}
|
|
|
|
$existingComparable = [
|
|
'medicine_code' => trim((string) ($existing['medicine_code'] ?? '')),
|
|
'name' => trim((string) ($existing['name'] ?? '')),
|
|
'brand' => trim((string) ($existing['brand'] ?? '')),
|
|
'unit' => trim((string) ($existing['unit'] ?? '')),
|
|
'settlement_price' => self::decimal($existing['settlement_price'] ?? 0),
|
|
'retail_price' => self::decimal($existing['retail_price'] ?? 0),
|
|
'status' => (int) ($existing['status'] ?? 0),
|
|
'catalog_version' => max(0, (int) ($existing['catalog_version'] ?? 0)),
|
|
'remote_deleted' => (int) ($existing['remote_deleted'] ?? 0),
|
|
];
|
|
$deactivated = $existingComparable['status'] === 1 && $values['status'] === 0 ? 1 : 0;
|
|
|
|
return [
|
|
'action' => $existingComparable === $values ? 'unchanged' : 'updated',
|
|
'values' => $values,
|
|
'deactivated' => $deactivated,
|
|
];
|
|
}
|
|
|
|
private static function decimal(mixed $value): string
|
|
{
|
|
return number_format(max(0.0, (float) $value), 4, '.', '');
|
|
}
|
|
}
|