add payment_link and banks
This commit is contained in:
1 parent
2ae79b1ca4
commit
de3135b588
100 files changed
+6167
-47
No files matched your search
@@ -0,0 +1,269 @@
|
||||
# Generated by Django 5.1.1 on 2026-10-05 10:44
|
||||
|
||||
import apps.payments.models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('notifications', '0017_alter_smstemplate_event_type'),
|
||||
('staff', '0012_organizationalunit_supervisor_mobile_5'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BankInstitution',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('code', models.SlugField(unique=True)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('historical', models.BooleanField(default=False)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['historical', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentAccessConfiguration',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('singleton', models.PositiveSmallIntegerField(default=1, editable=False, unique=True)),
|
||||
('open_view', models.BooleanField(default=True, verbose_name='مشاهده برای همه کارشناسان فعال')),
|
||||
('open_create', models.BooleanField(default=True, verbose_name='ایجاد برای همه کارشناسان فعال')),
|
||||
],
|
||||
options={
|
||||
'permissions': [('manage_payment_access', 'Manage payment access')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CompanyBankAccount',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('public_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||
('name', models.CharField(max_length=120, verbose_name='نام نمایشی حساب')),
|
||||
('holder', models.CharField(max_length=150, verbose_name='صاحب حساب')),
|
||||
('account_number', models.CharField(blank=True, max_length=40, verbose_name='شماره حساب')),
|
||||
('iban', models.CharField(blank=True, max_length=26, verbose_name='شبا')),
|
||||
('card_number', models.CharField(blank=True, max_length=16, verbose_name='شماره کارت')),
|
||||
('requires_deposit_id', models.BooleanField(default=False, verbose_name='شناسه واریز الزامی')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='فعال کلی')),
|
||||
('available_to_staff', models.BooleanField(default=True, verbose_name='قابل انتخاب کارشناسان')),
|
||||
('available_to_customers', models.BooleanField(default=True, verbose_name='قابل انتخاب مشتریان')),
|
||||
('sort_order', models.PositiveIntegerField(default=0)),
|
||||
('bank', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='payments.bankinstitution', verbose_name='بانک')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['sort_order', 'name', 'pk'],
|
||||
'permissions': [('manage_company_bankaccounts', 'Manage company bank accounts')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentAuditEvent',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('actor_name', models.CharField(max_length=150)),
|
||||
('action', models.CharField(max_length=50)),
|
||||
('object_type', models.CharField(max_length=50)),
|
||||
('object_id', models.CharField(max_length=64)),
|
||||
('details', models.JSONField(default=dict)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('actor', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at', '-pk'],
|
||||
'permissions': [('view_payment_audit', 'View financial audit')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentNotice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('public_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||
('account_snapshot', models.JSONField()),
|
||||
('subject', models.CharField(max_length=200)),
|
||||
('amount', models.BigIntegerField()),
|
||||
('deposit_id', models.CharField(blank=True, max_length=100)),
|
||||
('invoice_number', models.CharField(blank=True, max_length=100)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('cancelled_at', models.DateTimeField(blank=True, null=True)),
|
||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='payments.companybankaccount')),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='issued_payment_notices', to=settings.AUTH_USER_MODEL)),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='payment_notices', to=settings.AUTH_USER_MODEL)),
|
||||
('owning_unit', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='staff.organizationalunit')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at', '-pk'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentNotification',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('context', models.JSONField()),
|
||||
('rendered_body', models.TextField(blank=True)),
|
||||
('template_snapshot', models.JSONField(default=dict)),
|
||||
('job', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='payment_notification', to='notifications.notificationjob')),
|
||||
('notice', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='payments.paymentnotice')),
|
||||
('unit', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='staff.organizationalunit')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentNotificationAttempt',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('started_at', models.DateTimeField(auto_now_add=True)),
|
||||
('finished_at', models.DateTimeField(null=True)),
|
||||
('success', models.BooleanField(null=True)),
|
||||
('provider_code', models.CharField(blank=True, max_length=50)),
|
||||
('provider_id', models.CharField(blank=True, max_length=500)),
|
||||
('error', models.TextField(blank=True)),
|
||||
('notification', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='attempt_log', to='payments.paymentnotification')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentReceipt',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('public_id', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||
('status', models.CharField(choices=[('PENDING', 'در انتظار بررسی'), ('PARTIAL', 'تأیید ناقص'), ('APPROVED', 'تأیید شده'), ('REJECTED', 'رد شده')], db_index=True, default='PENDING', max_length=12)),
|
||||
('submission_key', models.UUIDField(unique=True)),
|
||||
('current_version', models.PositiveIntegerField(default=1)),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='payment_receipts', to=settings.AUTH_USER_MODEL)),
|
||||
('notice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='receipts', to='payments.paymentnotice')),
|
||||
('owning_unit', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='staff.organizationalunit')),
|
||||
('submitted_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='submitted_payment_receipts', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at', '-pk'],
|
||||
'permissions': [('approve_paymentreceipt', 'Approve payment receipt'), ('senior_approve_paymentreceipt', 'Senior final approval'), ('submit_customer_receipt', 'Submit receipt for customer'), ('view_all_payments', 'View payments across units')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentReceiptRevision',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('version', models.PositiveIntegerField()),
|
||||
('amount', models.BigIntegerField()),
|
||||
('paid_date', models.DateField()),
|
||||
('account_snapshot', models.JSONField()),
|
||||
('origin_other', models.CharField(blank=True, max_length=100)),
|
||||
('deposit_id', models.CharField(blank=True, max_length=100)),
|
||||
('tracking_number', models.CharField(blank=True, max_length=100)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('destination_mismatch', models.BooleanField(default=False)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('image', models.FileField(max_length=500, upload_to=apps.payments.models.receipt_path)),
|
||||
('mime_type', models.CharField(max_length=100)),
|
||||
('sha256', models.CharField(max_length=64)),
|
||||
('original_name', models.CharField(max_length=255)),
|
||||
('file_size', models.PositiveBigIntegerField()),
|
||||
('destination', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='payments.companybankaccount')),
|
||||
('origin_bank', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='payments.bankinstitution')),
|
||||
('receipt', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='revisions', to='payments.paymentreceipt')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ReceiptApprovalCycle',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('policy_snapshot', models.JSONField(default=dict)),
|
||||
('status', models.CharField(choices=[('PENDING', 'در انتظار بررسی'), ('PARTIAL', 'تأیید ناقص'), ('APPROVED', 'تأیید شده'), ('REJECTED', 'رد شده')], default='PENDING', max_length=12)),
|
||||
('finalized_at', models.DateTimeField(blank=True, null=True)),
|
||||
('finalized_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
('revision', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='cycle', to='payments.paymentreceiptrevision')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ReceiptApprovalAssignment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_senior', models.BooleanField(default=False)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
('cycle', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='assignments', to='payments.receiptapprovalcycle')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='paymentnotification',
|
||||
name='cycle',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='payments.receiptapprovalcycle'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ReceiptDecision',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('action', models.CharField(choices=[('APPROVE', 'تأیید'), ('REJECT', 'رد'), ('SENIOR_APPROVE', 'تأیید ارشد')], max_length=20)),
|
||||
('reason', models.TextField(blank=True)),
|
||||
('actor_name', models.CharField(max_length=150)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('cycle', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='decisions', to='payments.receiptapprovalcycle')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentApprovalPolicy',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=100, verbose_name='عنوان سیاست')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('reviewers', models.ManyToManyField(related_name='payment_review_policies', to=settings.AUTH_USER_MODEL, verbose_name='افراد الزامی تأیید')),
|
||||
('seniors', models.ManyToManyField(blank=True, related_name='payment_senior_policies', to=settings.AUTH_USER_MODEL, verbose_name='تأییدکنندگان ارشد')),
|
||||
('unit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='staff.organizationalunit', verbose_name='واحد (خالی: عمومی)')),
|
||||
],
|
||||
options={
|
||||
'permissions': [('manage_payment_approval_policy', 'Manage payment approval policy')],
|
||||
'constraints': [models.UniqueConstraint(condition=models.Q(('is_active', True), ('unit__isnull', False)), fields=('unit',), name='pay_active_unit_policy'), models.UniqueConstraint(condition=models.Q(('is_active', True), ('unit__isnull', True)), fields=('is_active',), name='pay_active_global_policy')],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentnotice',
|
||||
index=models.Index(fields=['customer', '-created_at'], name='payments_pa_custome_bc6e3f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentnotice',
|
||||
index=models.Index(fields=['owning_unit', '-created_at'], name='payments_pa_owning__f2b6af_idx'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='paymentnotice',
|
||||
constraint=models.CheckConstraint(condition=models.Q(('amount__gt', 0), ('amount__lte', 9007199254740991)), name='pay_notice_amount'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentreceipt',
|
||||
index=models.Index(fields=['customer', '-created_at'], name='payments_pa_custome_e266cc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentreceipt',
|
||||
index=models.Index(fields=['status', '-created_at'], name='payments_pa_status_5cd50b_idx'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='paymentreceiptrevision',
|
||||
constraint=models.UniqueConstraint(fields=('receipt', 'version'), name='pay_receipt_revision'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='paymentreceiptrevision',
|
||||
constraint=models.CheckConstraint(condition=models.Q(('amount__gt', 0), ('amount__lte', 9007199254740991)), name='pay_receipt_amount'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='receiptapprovalassignment',
|
||||
constraint=models.UniqueConstraint(fields=('cycle', 'user', 'is_senior'), name='pay_cycle_assignment'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='receiptdecision',
|
||||
constraint=models.UniqueConstraint(fields=('cycle', 'user'), name='pay_one_vote_per_user'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
from django.db import migrations
|
||||
|
||||
BANKS = [["melli","ملی ایران"],["mellat","ملت"],["saderat","صادرات ایران"],["sepah","سپه"],["tejarat","تجارت"],["refah","رفاه کارگران"],["keshavarzi","کشاورزی"],["maskan","مسکن"],["sanat-madan","صنعت و معدن"],["tosee-saderat","توسعه صادرات ایران"],["tosee-taavon","توسعه تعاون"],["post","پست بانک ایران"],["pasargad","پاسارگاد"],["parsian","پارسیان"],["saman","سامان"],["eghtesad-novin","اقتصاد نوین"],["karafarin","کارآفرین"],["sina","سینا"],["shahr","شهر"],["sarmayeh","سرمایه"],["dey","دی"],["gardeshgari","گردشگری"],["iran-zamin","ایران زمین"],["khavarmianeh","خاورمیانه"],["resalat","قرضالحسنه رسالت"],["mehr-iran","قرضالحسنه مهر ایران"],["iran-venezuela","مشترک ایران و ونزوئلا"],["melal","مؤسسه اعتباری ملل"],["ayandeh","آینده"],["ansar","انصار"],["ghavamin","قوامین"],["hekmat","حکمت ایرانیان"],["mehr-eghtesad","مهر اقتصاد"],["kosar","مؤسسه کوثر"],["noor","مؤسسه نور"],["tosee","مؤسسه توسعه"]]
|
||||
TAGS = ['recipient_name','company_name','notice_number','receipt_number','amount_rial','invoice_number','payment_link']
|
||||
|
||||
|
||||
def seed(apps, schema_editor):
|
||||
Bank = apps.get_model('payments', 'BankInstitution')
|
||||
Template = apps.get_model('notifications', 'SMSTemplate')
|
||||
for code, name in BANKS:
|
||||
Bank.objects.get_or_create(code=code, defaults={'name':name, 'historical':code in {'ayandeh','ansar','ghavamin','hekmat','mehr-eghtesad','kosar','noor','tosee'}})
|
||||
Bank.objects.get_or_create(code='other', defaults={'name': 'سایر'})
|
||||
for event, title, body in [
|
||||
('PAYMENT_NOTICE_CREATED','اعلامیه واریز','{recipient_name}، اعلامیه واریز {notice_number} به مبلغ {amount_rial} ریال برای شما صادر شد.\nمشاهده اطلاعات: {payment_link}\n{company_name}'),
|
||||
('PAYMENT_RECEIPT_APPROVED','تأیید فیش','{recipient_name}، فیش واریز شما به شماره {receipt_number} و مبلغ {amount_rial} ریال تأیید شد.\nپیگیری: {payment_link}\n{company_name}'),
|
||||
]:
|
||||
Template.objects.get_or_create(unit=None,event_type=event, defaults={'title':title,'body':body,'allowed_tags':TAGS,'is_active':True})
|
||||
apps.get_model('payments','PaymentAccessConfiguration').objects.get_or_create(singleton=1)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [('payments','0001_initial'), ('notifications','0017_alter_smstemplate_event_type')]
|
||||
operations = [migrations.RunPython(seed, migrations.RunPython.noop)]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.1 on 2026-10-05 11:05
|
||||
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('payments', '0002_initial_configuration'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='paymentnotice',
|
||||
name='issuance_key',
|
||||
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
|
||||
),
|
||||
]
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# Generated by Django 5.1.1 on 2026-10-06 05:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('payments', '0003_paymentnotice_issuance_key'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PaymentAccessLink',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('token_hash', models.CharField(editable=False, max_length=64, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('expires_at', models.DateTimeField()),
|
||||
('revoked_at', models.DateTimeField(blank=True, null=True)),
|
||||
('view_count', models.PositiveIntegerField(default=0)),
|
||||
('last_used_at', models.DateTimeField(blank=True, null=True)),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
|
||||
('notice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='payments.paymentnotice')),
|
||||
('receipt', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='payments.paymentreceipt')),
|
||||
('submitted_receipts', models.ManyToManyField(blank=True, related_name='access_links', to='payments.paymentreceipt')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'لینک موقت پرداخت',
|
||||
'verbose_name_plural': 'لینک\u200cهای موقت پرداخت',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='paymentnotification',
|
||||
name='access_link',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='payments.paymentaccesslink'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='paymentaccesslink',
|
||||
constraint=models.CheckConstraint(condition=models.Q(('notice__isnull', False), ('receipt__isnull', False), _connector='OR'), name='payment_link_has_target'),
|
||||
),
|
||||
]
|
||||
Whitespace-only changes.
Reference in new issue
Block a user