163 lines
6.5 KiB
PHP
163 lines
6.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\adminapi\logic\pharmacy;
|
|
|
|
use app\common\logic\BaseLogic;
|
|
use app\common\service\pharmacy\EjMedicineCatalogSyncService;
|
|
use app\common\service\pharmacy\EjMedicineMappingPolicy;
|
|
use think\facade\Db;
|
|
use think\facade\Config;
|
|
use Throwable;
|
|
|
|
class MedicineMappingLogic extends BaseLogic
|
|
{
|
|
public static function save(array $params, int $operatorId, string $operatorName): bool
|
|
{
|
|
self::$error = '';
|
|
try {
|
|
Db::transaction(function () use ($params, $operatorId, $operatorName): void {
|
|
$localId = (int) $params['local_medicine_id'];
|
|
$medicineCode = trim((string) $params['medicine_code']);
|
|
$local = Db::name('doctor_medicine')->where('id', $localId)->lock(true)->find();
|
|
$remote = Db::name('ej_medicine_catalog')->where('medicine_code', $medicineCode)->lock(true)->find();
|
|
EjMedicineMappingPolicy::assertValid($local ?: [], $remote ?: []);
|
|
|
|
$now = time();
|
|
$mapping = Db::name('ej_medicine_mapping')
|
|
->where('local_medicine_id', $localId)
|
|
->lock(true)
|
|
->find();
|
|
$values = [
|
|
'medicine_code' => $medicineCode,
|
|
'status' => 1,
|
|
'operator_id' => $operatorId,
|
|
'operator_name' => mb_substr(trim($operatorName), 0, 80),
|
|
'update_time' => $now,
|
|
'delete_time' => null,
|
|
];
|
|
if ($mapping) {
|
|
Db::name('ej_medicine_mapping')->where('id', (int) $mapping['id'])->update($values);
|
|
return;
|
|
}
|
|
Db::name('ej_medicine_mapping')->insert(array_merge($values, [
|
|
'local_medicine_id' => $localId,
|
|
'create_time' => $now,
|
|
]));
|
|
});
|
|
return true;
|
|
} catch (Throwable $exception) {
|
|
self::setError(self::isDuplicateKey($exception)
|
|
? '该本地药材映射刚被其他操作更新,请刷新后重试'
|
|
: $exception->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static function unlink(int $localMedicineId, int $operatorId, string $operatorName): bool
|
|
{
|
|
self::$error = '';
|
|
try {
|
|
Db::transaction(function () use ($localMedicineId, $operatorId, $operatorName): void {
|
|
$local = Db::name('doctor_medicine')->where('id', $localMedicineId)->lock(true)->find();
|
|
$mapping = Db::name('ej_medicine_mapping')
|
|
->where('local_medicine_id', $localMedicineId)
|
|
->lock(true)
|
|
->find();
|
|
$decision = EjMedicineMappingPolicy::unlinkDecision($local ?: [], $mapping ?: null);
|
|
if ($decision['already_unlinked']) {
|
|
return;
|
|
}
|
|
|
|
$now = time();
|
|
Db::name('ej_medicine_mapping')
|
|
->where('id', $decision['mapping_id'])
|
|
->where('local_medicine_id', $localMedicineId)
|
|
->where('status', 1)
|
|
->whereNull('delete_time')
|
|
->update([
|
|
'status' => 0,
|
|
'operator_id' => $operatorId,
|
|
'operator_name' => mb_substr(trim($operatorName), 0, 80),
|
|
'update_time' => $now,
|
|
'delete_time' => $now,
|
|
]);
|
|
});
|
|
return true;
|
|
} catch (Throwable $exception) {
|
|
self::setError($exception->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** @return array<string,mixed>|false */
|
|
public static function sync()
|
|
{
|
|
self::$error = '';
|
|
try {
|
|
return EjMedicineCatalogSyncService::sync(200);
|
|
} catch (Throwable $exception) {
|
|
self::setError($exception->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** @return array<string,mixed> */
|
|
public static function status(): array
|
|
{
|
|
$state = Db::name('ej_pharmacy_sync_state')->where('id', 1)->find() ?: [];
|
|
$catalogTotal = (int) Db::name('ej_medicine_catalog')->count();
|
|
$catalogActive = (int) Db::name('ej_medicine_catalog')
|
|
->where('status', 1)->where('remote_deleted', 0)->count();
|
|
$unmappedLocal = (int) Db::name('doctor_medicine')->alias('l')
|
|
->leftJoin(
|
|
'ej_medicine_mapping m',
|
|
'm.local_medicine_id = l.id AND m.status = 1 AND m.delete_time IS NULL'
|
|
)
|
|
->where('l.status', 1)
|
|
->whereNull('l.delete_time')
|
|
->whereNull('m.id')
|
|
->count('l.id');
|
|
|
|
return [
|
|
'sync_enabled' => (bool) Config::get('ej_pharmacy.catalog_sync_enabled', false),
|
|
'cursor' => (int) ($state['cursor'] ?? 0),
|
|
'last_success_time' => (int) ($state['last_success_time'] ?? 0),
|
|
'last_failure_time' => (int) ($state['last_failure_time'] ?? 0),
|
|
'last_error_summary' => (string) ($state['last_error_summary'] ?? ''),
|
|
'is_syncing' => !empty($state['lock_token']) && (int) ($state['lock_expires_at'] ?? 0) >= time(),
|
|
'catalog_total' => $catalogTotal,
|
|
'catalog_active' => $catalogActive,
|
|
'unmapped_local' => $unmappedLocal,
|
|
];
|
|
}
|
|
|
|
/** @return array<int,array<string,mixed>> */
|
|
public static function catalogOptions(string $keyword, int $limit = 30): array
|
|
{
|
|
$query = Db::name('ej_medicine_catalog')
|
|
->where('status', 1)
|
|
->where('remote_deleted', 0);
|
|
$keyword = trim($keyword);
|
|
if ($keyword !== '') {
|
|
$query->where(function ($nested) use ($keyword): void {
|
|
$nested->where('name', 'like', '%' . $keyword . '%')
|
|
->whereOr('medicine_code', 'like', '%' . $keyword . '%');
|
|
});
|
|
}
|
|
return $query
|
|
->field('medicine_code,name,brand,unit,settlement_price,retail_price,catalog_version,status')
|
|
->order('catalog_version', 'desc')
|
|
->limit(min(max($limit, 1), 50))
|
|
->select()
|
|
->toArray();
|
|
}
|
|
|
|
private static function isDuplicateKey(Throwable $exception): bool
|
|
{
|
|
return (string) $exception->getCode() === '23000'
|
|
|| str_contains(strtolower($exception->getMessage()), 'duplicate');
|
|
}
|
|
}
|