405 lines
13 KiB
Python
405 lines
13 KiB
Python
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
|