first push

This commit is contained in:
2026-01-18 12:05:56 +03:30
commit cdbb2e11ed
109 changed files with 3083 additions and 0 deletions

404
Wallet/models.py Normal file
View 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