first push
This commit is contained in:
404
Wallet/models.py
Normal file
404
Wallet/models.py
Normal file
@@ -0,0 +1,404 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from Authentication.models import BaseModel
|
||||
from django.contrib.auth.models import User
|
||||
from ArtaSystem import settings
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from django.urls import reverse
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from Wallet.errors import InsufficientBalance
|
||||
|
||||
from Wallet.processor import DPSPayProcessor
|
||||
|
||||
try: # available from Django1.4
|
||||
from django.utils.timezone import now
|
||||
except ImportError:
|
||||
now = datetime.now
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class Address(BaseModel):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_address", null=True)
|
||||
title = models.CharField(max_length=200, default="", null=True)
|
||||
country = models.CharField(max_length=100, default="", null=True)
|
||||
province = models.CharField(max_length=50, default="", null=True)
|
||||
# province = models.CharField(choices=provinces, max_length=50, default="", null=True)
|
||||
city = models.CharField(max_length=50, default="", null=True)
|
||||
# city = models.CharField(choices=cities, max_length=50, default="", null=True)
|
||||
street = models.CharField(default="", max_length=200, null=True)
|
||||
postal_code = models.CharField(max_length=20, default="", null=True)
|
||||
phone = models.CharField(max_length=20, default="", null=True)
|
||||
phone_type = models.CharField(max_length=20, default="", null=True)
|
||||
# phone_type = models.CharField(choices=phone_types, max_length=20, default="home", null=True)
|
||||
no = models.CharField(max_length=5, default="", null=True)
|
||||
floor = models.IntegerField(default=0, null=True)
|
||||
unit = models.IntegerField(default=0, null=True)
|
||||
# geo_points = models.OneToOneField(
|
||||
# GEOPoints, default=None, on_delete=models.CASCADE, null=True
|
||||
# )
|
||||
|
||||
is_default = models.BooleanField(default=False, null=True)
|
||||
|
||||
# def get_geo_points(self):
|
||||
# return {"lang": self.geo_points.lang, "lat": self.geo_points.lat}
|
||||
#
|
||||
# def get_persian_address(self):
|
||||
# return (
|
||||
# "کشور %c - استان %c - شهر %c - خیابان %c - طبقه %c - واحد %c - پلاک %c"
|
||||
# % (
|
||||
# self.country,
|
||||
# self.province,
|
||||
# self.city,
|
||||
# self.street,
|
||||
# self.floor,
|
||||
# self.unit,
|
||||
# self.no,
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def get_english_address(self):
|
||||
# return (
|
||||
# "%c Country - %c Province - %c City - %c Street - %c Floor - %c Unit - %c No."
|
||||
# % (
|
||||
# self.country,
|
||||
# self.province,
|
||||
# self.city,
|
||||
# self.street,
|
||||
# self.floor,
|
||||
# self.unit,
|
||||
# self.no,
|
||||
# )
|
||||
# )
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(Address, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class BankCard(BaseModel):
|
||||
# CARD_TYPE_CHOICES = (
|
||||
# ('CB', "Carte Bleu / VISA / Mastercard"),
|
||||
# ('AMEX', "American Express"))
|
||||
|
||||
user = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, related_name="banks", null=True
|
||||
)
|
||||
card = models.CharField(max_length=16, null=True, default="")
|
||||
iban = models.CharField(max_length=100, null=True, default="")
|
||||
state = models.CharField(max_length=20, default="pending")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(BankCard, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class PaymentMethod(BaseModel):
|
||||
method_type = models.CharField(max_length=255, default="")
|
||||
user = models.ForeignKey(
|
||||
User, related_name="payment_user", on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.key)
|
||||
|
||||
|
||||
Card_Type = (
|
||||
("Credit", "Credit Card"),
|
||||
("Debit", "Debit Card"),
|
||||
)
|
||||
|
||||
Transaction_Type = (
|
||||
("send", "Send"),
|
||||
("request", "Request"),
|
||||
("transfer", "Transfer"),
|
||||
)
|
||||
|
||||
states = (
|
||||
("completed", "Complete!"),
|
||||
("requested", "Requested!"),
|
||||
("pending", "Pending!"),
|
||||
("confirmed", "Confirmed!"),
|
||||
)
|
||||
|
||||
Categories = (
|
||||
("Bank", "Bank Transfer"),
|
||||
("Utilities", "Bills & Utilities"),
|
||||
("Transportation", "Auto & Transport"),
|
||||
("Groceries", "Groceries"),
|
||||
("Food", "Food"),
|
||||
("Shopping", "Shopping"),
|
||||
("Health", "Healthcare"),
|
||||
("Education", "Education"),
|
||||
("Travel", "Travel"),
|
||||
("Housing", "Housing"),
|
||||
("Entertainment", "Entertainment"),
|
||||
("Others", "Others"),
|
||||
)
|
||||
|
||||
|
||||
def get_uuid4():
|
||||
return str(uuid4())
|
||||
|
||||
|
||||
def expiry_date_to_datetime(expiry_date):
|
||||
"""Convert a credit card expiry date to a datetime object.
|
||||
The datetime is the last day of the month.
|
||||
"""
|
||||
exp = datetime.strptime(expiry_date, "%m%y") # format: MMYY
|
||||
# to find the next month
|
||||
# - add 31 days (more than a month) to the first day of the current month
|
||||
# - replace the day to be "1"
|
||||
# - substract one day
|
||||
exp += timedelta(days=31)
|
||||
exp = exp.replace(day=1)
|
||||
exp -= timedelta(days=1)
|
||||
return exp
|
||||
|
||||
|
||||
class Account(models.Model):
|
||||
payment = models.OneToOneField(PaymentMethod, on_delete=models.CASCADE)
|
||||
balance = models.FloatField(default=0.00)
|
||||
|
||||
def __str__(self):
|
||||
return "Account: %s" % self.payment.user.username
|
||||
|
||||
def get_update_url(self):
|
||||
return reverse("account_transfer", kwargs={"pk": self.pk})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# ensure that the database only stores 2 decimal places
|
||||
self.balance = round(self.balance, 2)
|
||||
super(Account, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class Bank(models.Model):
|
||||
payment = models.OneToOneField(PaymentMethod, on_delete=models.CASCADE)
|
||||
owner_first_name = models.CharField(max_length=255, default=None)
|
||||
owner_last_name = models.CharField(max_length=255, default=None)
|
||||
routing_number = models.CharField(max_length=9, default=None)
|
||||
account_number = models.CharField(max_length=10, default=None)
|
||||
|
||||
def __str__(self):
|
||||
return "Bank: ****%s" % self.account_number[5:]
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("bank_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def get_update_url(self):
|
||||
return reverse("bank_update", kwargs={"pk": self.pk})
|
||||
|
||||
def get_delete_url(self):
|
||||
return reverse("bank_delete", kwargs={"pk": self.pk})
|
||||
|
||||
class Meta:
|
||||
unique_together = ("routing_number", "account_number")
|
||||
|
||||
|
||||
class Card(models.Model):
|
||||
payment = models.OneToOneField(PaymentMethod, on_delete=models.DO_NOTHING)
|
||||
card_type = models.CharField(max_length=45, choices=Card_Type)
|
||||
card_number = models.CharField(max_length=16, default=None)
|
||||
owner_first_name = models.CharField(max_length=45)
|
||||
owner_last_name = models.CharField(max_length=45)
|
||||
security_code = models.CharField(max_length=3, default=None)
|
||||
expiration_date = models.DateField(default=None)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("card_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def get_update_url(self):
|
||||
return reverse("card_update", kwargs={"pk": self.pk})
|
||||
|
||||
def get_delete_url(self):
|
||||
return reverse("card_delete", kwargs={"pk": self.pk})
|
||||
|
||||
def __str__(self):
|
||||
return "%s Card: ************%s" % (self.card_type, self.card_number[12:])
|
||||
|
||||
class Meta:
|
||||
unique_together = (
|
||||
"card_type",
|
||||
"owner_first_name",
|
||||
"owner_last_name",
|
||||
"card_number",
|
||||
"security_code",
|
||||
"expiration_date",
|
||||
)
|
||||
ordering = ["card_type", "card_number"]
|
||||
|
||||
|
||||
class Wallet(BaseModel):
|
||||
owner = models.ForeignKey(
|
||||
User,
|
||||
related_name="wallets",
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
credit_cards = models.ManyToManyField(
|
||||
BankCard,
|
||||
)
|
||||
credit = models.CharField(
|
||||
max_length=20,
|
||||
default="0",
|
||||
)
|
||||
card_expiry = models.DateTimeField(default=timezone.now, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("wallet")
|
||||
verbose_name_plural = _("wallets")
|
||||
|
||||
def is_valid(self):
|
||||
"""Return True if the card expiry date is in the future."""
|
||||
exp = expiry_date_to_datetime(self.card_expiry)
|
||||
today = datetime.today()
|
||||
return exp >= expiry_date_to_datetime(today.strftime("%m%y"))
|
||||
|
||||
def expires_this_month(self):
|
||||
"""Return True if the card expiry date is in this current month."""
|
||||
today = datetime.today().strftime("%m%y")
|
||||
return today == self.card_expiry
|
||||
|
||||
def make_payment(self, amount):
|
||||
"""Make a payment from this wallet."""
|
||||
pp = DPSPayProcessor()
|
||||
result, transaction, message = pp.make_wallet_payment(self.wallet_id, amount)
|
||||
if result:
|
||||
self.transaction_set.create(amount=amount, transaction_id=transaction)
|
||||
return result, message
|
||||
|
||||
def deposit(self, value):
|
||||
"""Deposits a value to the wallet.
|
||||
Also creates a new transaction with the deposit
|
||||
value.
|
||||
"""
|
||||
self.transaction_set.create(
|
||||
value=value, running_balance=self.current_balance + value
|
||||
)
|
||||
self.current_balance += value
|
||||
self.save()
|
||||
|
||||
def withdraw(self, value):
|
||||
"""Withdraw's a value from the wallet.
|
||||
Also creates a new transaction with the withdraw
|
||||
value.
|
||||
Should the withdrawn amount is greater than the
|
||||
balance this wallet currently has, it raises an
|
||||
:mod:`InsufficientBalance` error. This exception
|
||||
inherits from :mod:`django.db.IntegrityError`. So
|
||||
that it automatically rolls-back during a
|
||||
transaction lifecycle.
|
||||
"""
|
||||
if value > self.current_balance:
|
||||
raise InsufficientBalance("This wallet has insufficient balance.")
|
||||
|
||||
self.transaction_set.create(
|
||||
value=-value, running_balance=self.current_balance - value
|
||||
)
|
||||
self.current_balance -= value
|
||||
self.save()
|
||||
|
||||
def transfer(self, wallet, value):
|
||||
"""Transfers an value to another wallet.
|
||||
Uses `deposit` and `withdraw` internally.
|
||||
"""
|
||||
self.withdraw(value)
|
||||
wallet.deposit(value)
|
||||
|
||||
|
||||
class Shipping(BaseModel):
|
||||
client_address = models.OneToOneField(Address, on_delete=models.CASCADE, related_name="client_address", null=True)
|
||||
# supplier_address = models.OneToOneField(Address, related_name="supplier_address")
|
||||
client = models.OneToOneField(User, on_delete=models.CASCADE, related_name="shipping_client", null=True)
|
||||
supplier = models.OneToOneField(User, on_delete=models.CASCADE, related_name="shipping_supplier", null=True)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.supplier.username
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(Shipping, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class Transaction(BaseModel):
|
||||
"""Payment."""
|
||||
|
||||
wallet = models.ForeignKey(
|
||||
Wallet,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL, # do never ever delete
|
||||
help_text=_("Wallet holding payment information"),
|
||||
)
|
||||
date = models.DateTimeField(
|
||||
default=now, help_text=_("When the account was created")
|
||||
)
|
||||
amount = models.DecimalField(max_digits=12, decimal_places=5)
|
||||
|
||||
status = models.CharField(max_length=45, choices=states)
|
||||
transaction_type = models.CharField(
|
||||
max_length=45, choices=Transaction_Type, default=""
|
||||
)
|
||||
category = models.CharField(max_length=45, choices=Categories)
|
||||
# amount = models.FloatField(default=0.00)
|
||||
description = models.CharField(max_length=200, default=False)
|
||||
create_date = models.DateTimeField(default=now, editable=False)
|
||||
is_complete = models.BooleanField(default=False)
|
||||
receiver = models.ForeignKey(
|
||||
User, related_name="receiver", on_delete=models.PROTECT, default=""
|
||||
)
|
||||
creator = models.ForeignKey(
|
||||
User, related_name="creator", on_delete=models.PROTECT, default=""
|
||||
)
|
||||
payment_method = models.ForeignKey(
|
||||
PaymentMethod,
|
||||
related_name="payment_method",
|
||||
on_delete=models.PROTECT,
|
||||
default="",
|
||||
null=True,
|
||||
)
|
||||
|
||||
def check_status(self):
|
||||
return "status is: %c" % self.status
|
||||
|
||||
def set_status(self, state):
|
||||
self.status = state
|
||||
if state == "completed":
|
||||
self.is_complete = True
|
||||
return "Status %c has been set!"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("staff_tran_detail", kwargs={"pk": self.pk})
|
||||
|
||||
def get_delete_url(self):
|
||||
return reverse("staff_tran_delete", kwargs={"pk": self.pk})
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# ensure that the database only stores 2 decimal places
|
||||
self.amount = round(self.amount, 2)
|
||||
super(Transaction, self).save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.transaction_id)
|
||||
|
||||
class Meta:
|
||||
ordering = ("-date",)
|
||||
verbose_name = _("transaction")
|
||||
verbose_name_plural = _("transactions")
|
||||
|
||||
|
||||
class Factor(BaseModel):
|
||||
transaction = models.ForeignKey(
|
||||
Transaction,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL, # do never ever delete
|
||||
help_text=_("Transactions"),
|
||||
)
|
||||
|
||||
pass
|
||||
Reference in New Issue
Block a user