The pricing rows are keyed by the *target* tenant, but the tenant filter pins every read to the environment of whoever is asking. A clinic owner setting the free-visit price for one of their doctors was therefore blind to the row that already existed: each save inserted another one — the unique key does not stop it, because insurance_id is NULL for the free-visit row and MySQL does not treat NULLs as equal — and the following read was blind in the same way, so the panel kept showing the old value. From the outside it simply looked like the field would not save. Reads now run outside the filter, the same exception the tenant-insurance repository already makes for the same reason, with authorization still coming from resolveTargetEntity(). findOneForInsurance() takes the newest row so a tenant that already accumulated duplicates converges on the last value the user entered, and a migration collapses those leftovers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
49 lines
1.9 KiB
PHP
49 lines
1.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace DoctrineMigrations;
|
|
|
|
use Doctrine\DBAL\Schema\Schema;
|
|
use Doctrine\Migrations\AbstractMigration;
|
|
|
|
/**
|
|
* پاکسازی ردیفهای تکراریِ «قیمت ویزیت آزاد».
|
|
*
|
|
* قید یکتای (entity_type, entity_id, insurance_id) ردیف ویزیت آزاد را نمیپوشاند،
|
|
* چون `insurance_id` آنجا NULL است و MySQL چند NULL را تکراری نمیشمارد. تا وقتی
|
|
* خواندنِ ردیفِ موجود زیر TenantFilter کور بود، هر بار ذخیره یک ردیف تازه میساخت.
|
|
* تازهترین ردیف همان چیزی است که کاربر آخرینبار ثبت کرده، پس بقیه حذف میشوند.
|
|
*/
|
|
final class Version20260818131524 extends AbstractMigration
|
|
{
|
|
public function getDescription(): string
|
|
{
|
|
return 'Collapse duplicate free-visit pricing rows to the newest one per tenant';
|
|
}
|
|
|
|
public function up(Schema $schema): void
|
|
{
|
|
$this->addSql(<<<'SQL'
|
|
DELETE p FROM entity_insurance_pricing p
|
|
JOIN (
|
|
SELECT entity_type, entity_id, MAX(id) AS keep_id
|
|
FROM entity_insurance_pricing
|
|
WHERE insurance_id IS NULL
|
|
GROUP BY entity_type, entity_id
|
|
HAVING COUNT(*) > 1
|
|
) newest
|
|
ON newest.entity_type = p.entity_type
|
|
AND newest.entity_id = p.entity_id
|
|
WHERE p.insurance_id IS NULL
|
|
AND p.id <> newest.keep_id
|
|
SQL);
|
|
}
|
|
|
|
public function down(Schema $schema): void
|
|
{
|
|
// ردیفهای حذفشده تکراری بودند؛ برگرداندنشان همان وضعیت خراب را باز میسازد.
|
|
$this->throwIrreversibleMigrationException('Duplicate pricing rows are not restored.');
|
|
}
|
|
}
|