1
0
forked from erp-dev/erp

feat: notifier beta

This commit is contained in:
2026-04-13 12:30:33 +08:00
parent 646dbe7f18
commit 9512132bb9
50 changed files with 17830 additions and 2 deletions

View File

@@ -0,0 +1,86 @@
# Generated by Django 5.2.8 on 2026-04-10 02:39
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("basic_info", "0025_customer_uniq_customer_merchant_name"),
("contenttypes", "0002_remove_content_type_name"),
]
operations = [
migrations.CreateModel(
name="Mission",
fields=[
("created_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")),
("updated_at", models.DateTimeField(auto_now=True, verbose_name="更新时间")),
("id", models.BigAutoField(primary_key=True, serialize=False)),
("description", models.TextField(verbose_name="任务描述")),
("category", models.CharField(choices=[("general", "通用")], db_index=True, default="general", max_length=50, verbose_name="任务类型")),
("is_urgent", models.BooleanField(db_index=True, default=False, verbose_name="是否紧急")),
("is_completed", models.BooleanField(db_index=True, default=False, verbose_name="是否完成")),
("is_cancelled", models.BooleanField(db_index=True, default=False, verbose_name="是否取消")),
("cancelled_at", models.DateTimeField(blank=True, null=True, verbose_name="取消时间")),
("content_id", models.PositiveBigIntegerField(blank=True, db_index=True, null=True, verbose_name="关联对象ID")),
("cancelled_by", models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name="cancelled_missions", to="basic_info.employee", verbose_name="取消人")),
("content_type", models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="missions", to="contenttypes.contenttype", verbose_name="关联对象类型")),
("creator", models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="created_missions", to="basic_info.employee", verbose_name="任务创建者")),
],
options={
"verbose_name": "任务",
"verbose_name_plural": "任务",
},
),
migrations.CreateModel(
name="MissionParticipant",
fields=[
("created_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")),
("updated_at", models.DateTimeField(auto_now=True, verbose_name="更新时间")),
("id", models.BigAutoField(primary_key=True, serialize=False)),
("employee", models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="mission_participations", to="basic_info.employee", verbose_name="参与者")),
("mission", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="participants", to="mission.mission", verbose_name="任务")),
],
options={
"verbose_name": "任务参与者",
"verbose_name_plural": "任务参与者",
},
),
migrations.CreateModel(
name="MissionReply",
fields=[
("created_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")),
("updated_at", models.DateTimeField(auto_now=True, verbose_name="更新时间")),
("id", models.BigAutoField(primary_key=True, serialize=False)),
("content", models.TextField(verbose_name="回应内容")),
("replied_at", models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name="回应时间")),
("ends_task", models.BooleanField(db_index=True, default=False, verbose_name="是否结束任务")),
("is_rejected", models.BooleanField(db_index=True, default=False, verbose_name="是否被撤销")),
("rejected_at", models.DateTimeField(blank=True, null=True, verbose_name="撤销时间")),
("mission", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="replies", to="mission.mission", verbose_name="任务")),
("responder", models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name="mission_replies", to="basic_info.employee", verbose_name="回应者")),
],
options={
"verbose_name": "任务回应",
"verbose_name_plural": "任务回应",
"ordering": ["replied_at", "id"],
},
),
migrations.AddIndex(
model_name="mission",
index=models.Index(fields=["content_type", "content_id"], name="mission_mis_content_be6081_idx"),
),
migrations.AddIndex(
model_name="mission",
index=models.Index(fields=["is_completed", "is_cancelled"], name="mission_mis_is_comp_299039_idx"),
),
migrations.AddConstraint(
model_name="missionparticipant",
constraint=models.UniqueConstraint(fields=("mission", "employee"), name="unique_mission_participant"),
),
]

View File

@@ -0,0 +1,125 @@
# Generated by Django 5.2.8 on 2026-04-10 03:36
import django.db.models.deletion
from django.db import migrations, models
def populate_merchant_fields(apps, schema_editor):
Mission = apps.get_model("mission", "Mission")
MissionParticipant = apps.get_model("mission", "MissionParticipant")
MissionReply = apps.get_model("mission", "MissionReply")
for mission in Mission.objects.select_related("creator").filter(merchant__isnull=True):
mission.merchant_id = mission.creator.merchant_id
mission.save(update_fields=["merchant"])
for participant in MissionParticipant.objects.select_related("mission").filter(merchant__isnull=True):
participant.merchant_id = participant.mission.merchant_id
participant.save(update_fields=["merchant"])
for reply in MissionReply.objects.select_related("mission").filter(merchant__isnull=True):
reply.merchant_id = reply.mission.merchant_id
reply.save(update_fields=["merchant"])
class Migration(migrations.Migration):
dependencies = [
("basic_info", "0025_customer_uniq_customer_merchant_name"),
("mission", "0001_initial"),
]
operations = [
migrations.RemoveIndex(
model_name="mission",
name="mission_mis_content_be6081_idx",
),
migrations.RemoveIndex(
model_name="mission",
name="mission_mis_is_comp_299039_idx",
),
migrations.AddField(
model_name="mission",
name="merchant",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="missions",
to="basic_info.merchant",
verbose_name="所属商户",
),
),
migrations.AddField(
model_name="missionparticipant",
name="merchant",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="mission_participants",
to="basic_info.merchant",
verbose_name="所属商户",
),
),
migrations.AddField(
model_name="missionreply",
name="merchant",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="mission_replies",
to="basic_info.merchant",
verbose_name="所属商户",
),
),
migrations.AddField(
model_name="missionreply",
name="rejected_by",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="rejected_mission_replies",
to="basic_info.employee",
verbose_name="撤销人",
),
),
migrations.RunPython(populate_merchant_fields, migrations.RunPython.noop),
migrations.AlterField(
model_name="mission",
name="merchant",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="missions",
to="basic_info.merchant",
verbose_name="所属商户",
),
),
migrations.AlterField(
model_name="missionparticipant",
name="merchant",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="mission_participants",
to="basic_info.merchant",
verbose_name="所属商户",
),
),
migrations.AlterField(
model_name="missionreply",
name="merchant",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="mission_replies",
to="basic_info.merchant",
verbose_name="所属商户",
),
),
migrations.AddIndex(
model_name="mission",
index=models.Index(fields=["merchant", "content_type", "content_id"], name="mission_mis_merchan_af589f_idx"),
),
migrations.AddIndex(
model_name="mission",
index=models.Index(fields=["merchant", "is_completed", "is_cancelled"], name="mission_mis_merchan_6d6d37_idx"),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.8 on 2026-04-10 04:40
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mission', '0002_add_merchant_and_reply_rejected_by'),
]
operations = [
migrations.AlterModelOptions(
name='mission',
options={'permissions': [('reopen_mission', 'Can reopen mission'), ('reject_mission_reply', 'Can reject mission reply')], 'verbose_name': '任务', 'verbose_name_plural': '任务'},
),
]

View File

@@ -0,0 +1,101 @@
import django.db.models.deletion
from django.db import migrations, models
LEGACY_CATEGORY_NAME_MAP = {
"general": "通用",
}
def forwards(apps, schema_editor):
Mission = apps.get_model("mission", "Mission")
MissionCategory = apps.get_model("mission", "MissionCategory")
db_alias = schema_editor.connection.alias
category_cache = {}
for mission in Mission.objects.using(db_alias).all().only("id", "merchant_id", "category").iterator():
legacy_value = (mission.category or "").strip() or "general"
category_name = LEGACY_CATEGORY_NAME_MAP.get(legacy_value, legacy_value)
cache_key = (mission.merchant_id, category_name)
category = category_cache.get(cache_key)
if category is None:
category, _ = MissionCategory.objects.using(db_alias).get_or_create(
merchant_id=mission.merchant_id,
name=category_name,
)
category_cache[cache_key] = category
Mission.objects.using(db_alias).filter(id=mission.id).update(category_ref_id=category.id)
class Migration(migrations.Migration):
dependencies = [
("basic_info", "0025_customer_uniq_customer_merchant_name"),
("mission", "0003_alter_mission_options"),
]
operations = [
migrations.CreateModel(
name="MissionCategory",
fields=[
("created_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")),
("updated_at", models.DateTimeField(auto_now=True, verbose_name="更新时间")),
("id", models.BigAutoField(primary_key=True, serialize=False)),
("name", models.CharField(max_length=50, verbose_name="分类名称")),
(
"merchant",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="mission_categories",
to="basic_info.merchant",
verbose_name="所属商户",
),
),
],
options={
"verbose_name": "任务分类",
"verbose_name_plural": "任务分类",
"ordering": ["id"],
"constraints": [
models.UniqueConstraint(
fields=("merchant", "name"),
name="unique_mission_category_name_per_merchant",
),
],
},
),
migrations.AddField(
model_name="mission",
name="category_ref",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to="mission.missioncategory",
verbose_name="任务类型",
),
),
migrations.RunPython(forwards, migrations.RunPython.noop),
migrations.RemoveField(
model_name="mission",
name="category",
),
migrations.RenameField(
model_name="mission",
old_name="category_ref",
new_name="category",
),
migrations.AlterField(
model_name="mission",
name="category",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="missions",
to="mission.missioncategory",
verbose_name="任务类型",
),
),
]

View File