update VAT importer
This commit is contained in:
parent
50fd9e1f37
commit
e03cdf214a
6 changed files with 141 additions and 79 deletions
|
@ -60,6 +60,22 @@ python manage.py migrate
|
|||
python manage.py bootstrap-user --username nicocustomer
|
||||
#+END_SRC
|
||||
|
||||
** Initialise the database
|
||||
While it is not strictly required to add default values to the
|
||||
database, it might significantly reduce the starting time with
|
||||
uncloud.
|
||||
|
||||
To add the default database values run:
|
||||
|
||||
#+BEGIN_SRC shell
|
||||
# Add local objects
|
||||
python manage.py db-add-defaults
|
||||
|
||||
# Import VAT rates
|
||||
python manage.py import-vat-rates
|
||||
|
||||
#+END_SRC
|
||||
|
||||
* Testing / CLI Access
|
||||
Access via the commandline (CLI) can be done using curl or
|
||||
httpie. In our examples we will use httpie.
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
import random
|
||||
import string
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.conf import settings
|
||||
|
||||
from uncloud_pay.models import BillingAddress, RecurringPeriod, Product
|
||||
|
||||
from uncloud_pay.models import RecurringPeriod, Product
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Add standard uncloud values'
|
||||
|
@ -9,6 +16,24 @@ class Command(BaseCommand):
|
|||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# Order matters, objects are somewhat dependent on each other
|
||||
# Order matters, objects can be dependent on each other
|
||||
|
||||
admin_username="uncloud-admin"
|
||||
pw_length = 32
|
||||
|
||||
# Only set password if the user did not exist before
|
||||
try:
|
||||
admin_user = get_user_model().objects.get(username=settings.UNCLOUD_ADMIN_NAME)
|
||||
except ObjectDoesNotExist:
|
||||
random_password = ''.join(random.SystemRandom().choice(string.ascii_lowercase + string.digits) for _ in range(pw_length))
|
||||
|
||||
admin_user = get_user_model().objects.create_user(username=settings.UNCLOUD_ADMIN_NAME, password=random_password)
|
||||
admin_user.is_superuser=True
|
||||
admin_user.is_staff=True
|
||||
admin_user.save()
|
||||
|
||||
print(f"Created admin user '{admin_username}' with password '{random_password}'")
|
||||
|
||||
BillingAddress.populate_db_defaults()
|
||||
RecurringPeriod.populate_db_defaults()
|
||||
Product.populate_db_defaults()
|
||||
|
|
|
@ -19,8 +19,6 @@ from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
|
|||
|
||||
LOGGING = {}
|
||||
|
||||
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
@ -185,6 +183,8 @@ ALLOWED_HOSTS = []
|
|||
# required for hardcopy / pdf rendering: https://github.com/loftylabs/django-hardcopy
|
||||
CHROME_PATH = '/usr/bin/chromium-browser'
|
||||
|
||||
# Username that is created by default and owns the configuration objects
|
||||
UNCLOUD_ADMIN_NAME = "uncloud-admin"
|
||||
|
||||
# Overwrite settings with local settings, if existing
|
||||
try:
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.http import FileResponse
|
|||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
from uncloud_pay.models import Bill, Order, BillRecord, BillingAddress, Product, RecurringPeriod, ProductToRecurringPeriod
|
||||
from uncloud_pay.models import *
|
||||
|
||||
|
||||
class BillRecordInline(admin.TabularInline):
|
||||
|
@ -88,10 +88,5 @@ admin.site.register(Bill, BillAdmin)
|
|||
admin.site.register(ProductToRecurringPeriod)
|
||||
admin.site.register(Product, ProductAdmin)
|
||||
|
||||
#admin.site.register(Order, OrderAdmin)
|
||||
#for m in [ SampleOneTimeProduct, SampleRecurringProduct, SampleRecurringProductOneTimeFee ]:
|
||||
|
||||
admin.site.register(Order)
|
||||
admin.site.register(BillRecord)
|
||||
admin.site.register(BillingAddress)
|
||||
admin.site.register(RecurringPeriod)
|
||||
for m in [ Order, BillRecord, BillingAddress, RecurringPeriod, VATRate ]:
|
||||
admin.site.register(m)
|
||||
|
|
|
@ -1,44 +1,35 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from uncloud_pay.models import VATRate
|
||||
import csv
|
||||
|
||||
import urllib
|
||||
import csv
|
||||
import sys
|
||||
import io
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Imports VAT Rates. Assume vat rates of format https://github.com/kdeldycke/vat-rates/blob/master/vat_rates.csv'''
|
||||
vat_url = "https://raw.githubusercontent.com/kdeldycke/vat-rates/main/vat_rates.csv"
|
||||
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('csv_file', nargs='+', type=str)
|
||||
parser.add_argument('--vat-url', default=self.vat_url)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
for c_file in options['csv_file']:
|
||||
print("c_file = %s" % c_file)
|
||||
with open(c_file, mode='r') as csv_file:
|
||||
csv_reader = csv.DictReader(csv_file)
|
||||
line_count = 0
|
||||
for row in csv_reader:
|
||||
if line_count == 0:
|
||||
line_count += 1
|
||||
vat_url = options['vat_url']
|
||||
url_open = urllib.request.urlopen(vat_url)
|
||||
|
||||
# map to fileio using stringIO
|
||||
csv_file = io.StringIO(url_open.read().decode('utf-8'))
|
||||
reader = csv.DictReader(csv_file)
|
||||
|
||||
for row in reader:
|
||||
# print(row)
|
||||
obj, created = VATRate.objects.get_or_create(
|
||||
starting_date=row["start_date"],
|
||||
ending_date=row["stop_date"] if row["stop_date"] is not "" else None,
|
||||
ending_date=row["stop_date"] if row["stop_date"] != "" else None,
|
||||
territory_codes=row["territory_codes"],
|
||||
currency_code=row["currency_code"],
|
||||
rate=row["rate"],
|
||||
rate_type=row["rate_type"],
|
||||
description=row["description"]
|
||||
)
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
'%s. %s - %s - %s - %s' % (
|
||||
line_count,
|
||||
obj.start_date,
|
||||
obj.stop_date,
|
||||
obj.territory_codes,
|
||||
obj.rate
|
||||
)
|
||||
))
|
||||
line_count+=1
|
||||
|
||||
except Exception as e:
|
||||
print(" *** Error occurred. Details {}".format(str(e)))
|
||||
|
|
|
@ -1,28 +1,26 @@
|
|||
import logging
|
||||
import itertools
|
||||
import datetime
|
||||
from math import ceil
|
||||
from calendar import monthrange
|
||||
from decimal import Decimal
|
||||
from functools import reduce
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.utils import timezone
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
import logging
|
||||
from functools import reduce
|
||||
import itertools
|
||||
from math import ceil
|
||||
import datetime
|
||||
from calendar import monthrange
|
||||
from decimal import Decimal
|
||||
from django.conf import settings
|
||||
|
||||
import uncloud_pay.stripe
|
||||
from uncloud_pay import AMOUNT_DECIMALS, AMOUNT_MAX_DIGITS, COUNTRIES
|
||||
from uncloud.models import UncloudModel, UncloudStatus
|
||||
|
||||
from decimal import Decimal
|
||||
import decimal
|
||||
|
||||
# Used to generate bill due dates.
|
||||
BILL_PAYMENT_DELAY=datetime.timedelta(days=10)
|
||||
|
@ -102,22 +100,6 @@ class StripeCustomer(models.Model):
|
|||
on_delete=models.CASCADE)
|
||||
stripe_id = models.CharField(max_length=32)
|
||||
|
||||
###
|
||||
# Hosting company configuration
|
||||
|
||||
class HostingProvider(models.Model):
|
||||
"""
|
||||
A class resembling who is running this uncloud instance.
|
||||
This might change over time so we allow starting/ending dates
|
||||
|
||||
This also defines the taxation rules
|
||||
|
||||
WIP.
|
||||
"""
|
||||
starting_date = models.DateField()
|
||||
ending_date = models.DateField()
|
||||
|
||||
|
||||
|
||||
###
|
||||
# Payments and Payment Methods.
|
||||
|
@ -267,7 +249,6 @@ class RecurringPeriod(models.Model):
|
|||
obj, created = cls.objects.get_or_create(name=name,
|
||||
defaults={ 'duration_seconds': seconds })
|
||||
|
||||
|
||||
@staticmethod
|
||||
def secs_to_name(secs):
|
||||
name = ""
|
||||
|
@ -290,14 +271,11 @@ class RecurringPeriod(models.Model):
|
|||
return f"{self.name} ({duration})"
|
||||
|
||||
|
||||
|
||||
|
||||
###
|
||||
# Bills.
|
||||
|
||||
class BillingAddress(models.Model):
|
||||
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
|
||||
|
||||
organization = models.CharField(max_length=100, blank=True, null=True)
|
||||
name = models.CharField(max_length=100)
|
||||
street = models.CharField(max_length=100)
|
||||
|
@ -314,6 +292,32 @@ class BillingAddress(models.Model):
|
|||
name='one_active_billing_address_per_user')
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def populate_db_defaults(cls):
|
||||
"""
|
||||
Ensure we have at least one billing address that is associated with the uncloud-admin.
|
||||
|
||||
This way we are sure that an UncloudProvider can be created.
|
||||
|
||||
Cannot use get_or_create as that looks for exactly one.
|
||||
|
||||
"""
|
||||
|
||||
owner = get_user_model().objects.get(username=settings.UNCLOUD_ADMIN_NAME)
|
||||
billing_address = cls.objects.filter(owner=owner).first()
|
||||
|
||||
if not billing_address:
|
||||
billing_address = cls.objects.create(owner=owner,
|
||||
organization="uncloud admins",
|
||||
name="Uncloud Admin",
|
||||
street="Uncloudstreet. 42",
|
||||
city="Luchsingen",
|
||||
postal_code="8775",
|
||||
country="CH",
|
||||
active=True)
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_address_for(user):
|
||||
return BillingAddress.objects.get(owner=user, active=True)
|
||||
|
@ -349,6 +353,10 @@ class VATRate(models.Model):
|
|||
logger.debug("Did not find VAT rate for %s, returning 0" % country_code)
|
||||
return 0
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.territory_codes}: {self.starting_date} - {self.ending_date}: {self.rate_type}"
|
||||
|
||||
###
|
||||
# Products
|
||||
|
||||
|
@ -1205,3 +1213,30 @@ class ProductToRecurringPeriod(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return f"{self.product} - {self.recurring_period} (default: {self.is_default})"
|
||||
|
||||
|
||||
###
|
||||
# Who is running / providing this instance of uncloud?
|
||||
|
||||
class UncloudProvider(models.Model):
|
||||
"""
|
||||
A class resembling who is running this uncloud instance.
|
||||
This might change over time so we allow starting/ending dates
|
||||
|
||||
This also defines the taxation rules.
|
||||
|
||||
starting/ending date define from when to when this is valid. This way
|
||||
we can model address changes and have it correct in the bills.
|
||||
"""
|
||||
|
||||
valid_from = models.DateField()
|
||||
valid_to = models.DateField(blank=True)
|
||||
|
||||
billing_address = models.ForeignKey(BillingAddress, on_delete=models.CASCADE)
|
||||
|
||||
@classmethod
|
||||
def populate_db_defaults(cls):
|
||||
ba = BillingAddress.objects.get_or_create()
|
||||
# obj, created = cls.objects.get_or_create(
|
||||
# valid_from=timezone.now()
|
||||
# defaults={ 'duration_seconds': seconds })
|
||||
|
|
Loading…
Reference in a new issue