376 lines
12 KiB
Python
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}"
|