Files
ai_web/backend/apps/bounties/models.py
2026-01-28 16:00:56 +08:00

376 lines
12 KiB
Python

"""
Bounty models for task/reward system.
"""
from django.db import models
from django.conf import settings
class Bounty(models.Model):
"""Bounty/Reward tasks."""
class Status(models.TextChoices):
OPEN = 'open', '开放中'
IN_PROGRESS = 'in_progress', '进行中'
COMPLETED = 'completed', '已完成'
CANCELLED = 'cancelled', '已取消'
DISPUTED = 'disputed', '争议中'
id = models.AutoField(primary_key=True)
title = models.CharField('标题', max_length=300)
description = models.TextField('描述')
reward = models.DecimalField('赏金', max_digits=10, decimal_places=2)
currency = models.CharField('货币', max_length=10, default='CNY')
publisher = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='published_bounties',
verbose_name='发布者'
)
acceptor = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='accepted_bounties',
verbose_name='接单者'
)
status = models.CharField(
'状态',
max_length=20,
choices=Status.choices,
default=Status.OPEN
)
deadline = models.DateTimeField('截止时间', blank=True, null=True)
completed_at = models.DateTimeField('完成时间', blank=True, null=True)
# Stripe payment fields
stripe_payment_intent_id = models.CharField(
'Stripe支付意向ID',
max_length=255,
blank=True,
null=True
)
stripe_transfer_id = models.CharField(
'Stripe转账ID',
max_length=255,
blank=True,
null=True
)
is_paid = models.BooleanField('是否已付款', default=False)
is_escrowed = models.BooleanField('是否已托管', default=False)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
db_table = 'bounties'
verbose_name = '悬赏'
verbose_name_plural = '悬赏'
ordering = ['-created_at']
indexes = [
models.Index(fields=["status", "created_at"]),
models.Index(fields=["publisher", "created_at"]),
models.Index(fields=["acceptor", "created_at"]),
]
def __str__(self):
return self.title
class BountyApplication(models.Model):
"""Bounty applications/bids."""
class Status(models.TextChoices):
PENDING = 'pending', '待审核'
ACCEPTED = 'accepted', '已接受'
REJECTED = 'rejected', '已拒绝'
id = models.AutoField(primary_key=True)
bounty = models.ForeignKey(
Bounty,
on_delete=models.CASCADE,
related_name='applications',
verbose_name='悬赏'
)
applicant = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='bounty_applications',
verbose_name='申请者'
)
message = models.TextField('申请消息', blank=True, null=True)
status = models.CharField(
'状态',
max_length=20,
choices=Status.choices,
default=Status.PENDING
)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
db_table = 'bountyApplications'
verbose_name = '悬赏申请'
verbose_name_plural = '悬赏申请'
unique_together = ['bounty', 'applicant']
indexes = [
models.Index(fields=["bounty", "status"]),
models.Index(fields=["applicant", "status"]),
]
def __str__(self):
return f"{self.applicant} -> {self.bounty.title}"
class BountyComment(models.Model):
"""Bounty comments."""
id = models.AutoField(primary_key=True)
bounty = models.ForeignKey(
Bounty,
on_delete=models.CASCADE,
related_name='comments',
verbose_name='悬赏'
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='bounty_comments',
verbose_name='用户'
)
content = models.TextField('内容')
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='replies',
verbose_name='父评论'
)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
db_table = 'bountyComments'
verbose_name = '悬赏评论'
verbose_name_plural = '悬赏评论'
ordering = ['created_at']
indexes = [
models.Index(fields=["bounty", "created_at"]),
models.Index(fields=["parent", "created_at"]),
]
def __str__(self):
return f"{self.user} on {self.bounty.title}"
class BountyDelivery(models.Model):
"""Bounty delivery submissions."""
class Status(models.TextChoices):
SUBMITTED = 'submitted', '已提交'
ACCEPTED = 'accepted', '已验收'
REJECTED = 'rejected', '已驳回'
id = models.AutoField(primary_key=True)
bounty = models.ForeignKey(
Bounty,
on_delete=models.CASCADE,
related_name='deliveries',
verbose_name='悬赏'
)
submitter = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='bounty_deliveries',
verbose_name='提交者'
)
content = models.TextField('交付内容')
attachment_url = models.TextField('附件链接', blank=True, null=True)
status = models.CharField(
'状态',
max_length=20,
choices=Status.choices,
default=Status.SUBMITTED
)
submitted_at = models.DateTimeField('提交时间', auto_now_add=True)
reviewed_at = models.DateTimeField('验收时间', blank=True, null=True)
class Meta:
db_table = 'bountyDeliveries'
verbose_name = '悬赏交付'
verbose_name_plural = '悬赏交付'
ordering = ['-submitted_at']
indexes = [
models.Index(fields=["bounty", "status"]),
models.Index(fields=["submitted_at"]),
]
def __str__(self):
return f"{self.bounty.title} - {self.submitter}"
class BountyDispute(models.Model):
"""Dispute records for bounties."""
class Status(models.TextChoices):
OPEN = 'open', '处理中'
RESOLVED = 'resolved', '已解决'
REJECTED = 'rejected', '已驳回'
id = models.AutoField(primary_key=True)
bounty = models.ForeignKey(
Bounty,
on_delete=models.CASCADE,
related_name='disputes',
verbose_name='悬赏'
)
initiator = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='bounty_disputes',
verbose_name='发起人'
)
reason = models.TextField('争议原因')
evidence_url = models.TextField('证据链接', blank=True, null=True)
status = models.CharField(
'状态',
max_length=20,
choices=Status.choices,
default=Status.OPEN
)
resolution = models.TextField('处理结果', blank=True, null=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
resolved_at = models.DateTimeField('处理时间', blank=True, null=True)
class Meta:
db_table = 'bountyDisputes'
verbose_name = '悬赏争议'
verbose_name_plural = '悬赏争议'
ordering = ['-created_at']
indexes = [
models.Index(fields=["bounty", "status"]),
models.Index(fields=["status", "created_at"]),
]
def __str__(self):
return f"{self.bounty.title} - {self.initiator}"
class BountyReview(models.Model):
"""Mutual reviews for completed bounties."""
id = models.AutoField(primary_key=True)
bounty = models.ForeignKey(
Bounty,
on_delete=models.CASCADE,
related_name='reviews',
verbose_name='悬赏'
)
reviewer = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='bounty_reviews_given',
verbose_name='评价者'
)
reviewee = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='bounty_reviews_received',
verbose_name='被评价者'
)
rating = models.PositiveSmallIntegerField('评分')
comment = models.TextField('评价内容', blank=True, null=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
db_table = 'bountyReviews'
verbose_name = '悬赏评价'
verbose_name_plural = '悬赏评价'
unique_together = ['bounty', 'reviewer']
ordering = ['-created_at']
indexes = [
models.Index(fields=["bounty", "created_at"]),
models.Index(fields=["reviewee", "created_at"]),
]
def __str__(self):
return f"{self.bounty.title} - {self.reviewer}"
class PaymentEvent(models.Model):
"""Stripe webhook event log for idempotency."""
id = models.AutoField(primary_key=True)
event_id = models.CharField('事件ID', max_length=255, unique=True)
event_type = models.CharField('事件类型', max_length=100)
bounty = models.ForeignKey(
Bounty,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='payment_events',
verbose_name='悬赏'
)
payload = models.TextField('事件内容')
processed_at = models.DateTimeField('处理时间', auto_now_add=True)
success = models.BooleanField('是否成功', default=True)
error_message = models.TextField('错误信息', blank=True, null=True)
class Meta:
db_table = 'paymentEvents'
verbose_name = '支付事件'
verbose_name_plural = '支付事件'
ordering = ['-processed_at']
def __str__(self):
return f"{self.event_type} - {self.event_id}"
class BountyExtensionRequest(models.Model):
"""Deadline extension request for bounty."""
class Status(models.TextChoices):
PENDING = 'pending', '待处理'
APPROVED = 'approved', '已同意'
REJECTED = 'rejected', '已拒绝'
id = models.AutoField(primary_key=True)
bounty = models.ForeignKey(
Bounty,
on_delete=models.CASCADE,
related_name='extension_requests',
verbose_name='悬赏'
)
requester = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='bounty_extension_requests',
verbose_name='申请人'
)
proposed_deadline = models.DateTimeField('申请截止时间')
reason = models.TextField('申请原因', blank=True, null=True)
status = models.CharField(
'状态',
max_length=20,
choices=Status.choices,
default=Status.PENDING
)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
reviewed_at = models.DateTimeField('处理时间', blank=True, null=True)
class Meta:
db_table = 'bountyExtensionRequests'
verbose_name = '延期申请'
verbose_name_plural = '延期申请'
ordering = ['-created_at']
indexes = [
models.Index(fields=["bounty", "status"]),
models.Index(fields=["requester", "status"]),
]
def __str__(self):
return f"{self.bounty.title} - {self.requester}"