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