Merge branch 'master' into feature/6561/invoices-webhook
This commit is contained in:
commit
6873d901c9
25 changed files with 892 additions and 182 deletions
34
Changelog
34
Changelog
|
|
@ -1,3 +1,37 @@
|
||||||
|
2.6.10: 2019-11-16
|
||||||
|
* translation: Add DE translations for features in 2.6.{8,9} by moep (MR!719)
|
||||||
|
2.6.9: 2019-11-15
|
||||||
|
* feature: Allow creating yearly subscriptions for Generic Products (MR!718)
|
||||||
|
Notes for deployment:
|
||||||
|
- do a db migrate for new column added to Generic Product model
|
||||||
|
./manage.py migrate hosting
|
||||||
|
2.6.8: 2019-11-15
|
||||||
|
* feature: [EU VAT] Add EU VAT feature for generic products (MR!717)
|
||||||
|
Notes for deployment:
|
||||||
|
- do a db migrate a to create VATRates table
|
||||||
|
./manage.py migrate hosting
|
||||||
|
- load vat_rates.csv
|
||||||
|
./manage.py import_vat_rates vat_rates.csv
|
||||||
|
2.6.7: 2019-11-04
|
||||||
|
* bugfix: [admin] Improve dumpuser: show proper dates + bugfix
|
||||||
|
* bugfix: [admin] Improve fetch_stripe_bills:
|
||||||
|
- fix wrong assigment of string to num_invoice_created
|
||||||
|
variable,
|
||||||
|
- return None (do not handle the case) if we don't have an
|
||||||
|
order
|
||||||
|
* bugfix: [admin] Improve deleteuser: do not delete order, bill and vm_detail
|
||||||
|
2.6.6: 2019-11-04
|
||||||
|
* feature: [admin] Add dumpuser management command that dumps a user's data in json (MR!716)
|
||||||
|
2.6.5: 2019-09-24
|
||||||
|
* #7169: [hosting] Fix server error while vm terminate takes longer than 30 seconds
|
||||||
|
* #7170: [hosting] Improve admin email body contents for hosting vm terminate error case
|
||||||
|
2.6.4: 2019-09-15
|
||||||
|
* #7147: [OpenBSD vm] Add an explanatory text for username puffy on OpenBSD (MR!714)
|
||||||
|
2.6.3: 2019-08-28
|
||||||
|
* #7032: [hosting] Bugfix: Reentering the same SSH key used before does allow user to proceed further; complains key exists (MR!712)
|
||||||
|
* #7070: [check_vm/api] Bugfix: Provide oneadmin credentials to check whether a user is the owner of a VM (MR!713)
|
||||||
|
2.6.2: 2019-08-22
|
||||||
|
* #7068: [django/node/rails] Remove public- prefix from OS template names (MR!711)
|
||||||
2.6.1: 2019-07-09
|
2.6.1: 2019-07-09
|
||||||
* #6941: [hosting dashboard] Show the card's expiry year & month too in the list of added cards (MR!710)
|
* #6941: [hosting dashboard] Show the card's expiry year & month too in the list of added cards (MR!710)
|
||||||
2.6: 2019-07-03
|
2.6: 2019-07-03
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2019-07-03 11:18+0000\n"
|
"POT-Creation-Date: 2019-11-15 17:33+0000\n"
|
||||||
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
|
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
|
||||||
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
|
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|
@ -20,7 +20,7 @@ msgstr ""
|
||||||
"X-Translated-Using: django-rosetta 0.8.1\n"
|
"X-Translated-Using: django-rosetta 0.8.1\n"
|
||||||
|
|
||||||
msgid "CMS Favicon"
|
msgid "CMS Favicon"
|
||||||
msgstr ""
|
msgstr "CMS Favicon"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Your New VM %(vm_name)s at Data Center Light"
|
msgid "Your New VM %(vm_name)s at Data Center Light"
|
||||||
|
|
@ -52,7 +52,7 @@ msgid "Login"
|
||||||
msgstr "Anmelden"
|
msgstr "Anmelden"
|
||||||
|
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr ""
|
msgstr "Dashboard"
|
||||||
|
|
||||||
msgid "Thank you for contacting us."
|
msgid "Thank you for contacting us."
|
||||||
msgstr "Nachricht gesendet."
|
msgstr "Nachricht gesendet."
|
||||||
|
|
@ -64,7 +64,7 @@ msgid "Get in touch with us!"
|
||||||
msgstr "Sende uns eine Nachricht."
|
msgstr "Sende uns eine Nachricht."
|
||||||
|
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr ""
|
msgstr "Name"
|
||||||
|
|
||||||
msgid "Please enter your name."
|
msgid "Please enter your name."
|
||||||
msgstr "Bitte gib Deinen Namen ein."
|
msgstr "Bitte gib Deinen Namen ein."
|
||||||
|
|
@ -108,7 +108,7 @@ msgid "Your account details are as follows"
|
||||||
msgstr "Deine Account Details sind unten aufgelistet"
|
msgstr "Deine Account Details sind unten aufgelistet"
|
||||||
|
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
msgstr "Username"
|
msgstr "Benutzername"
|
||||||
|
|
||||||
msgid "Your email address"
|
msgid "Your email address"
|
||||||
msgstr "Deine E-Mail-Adresse"
|
msgstr "Deine E-Mail-Adresse"
|
||||||
|
|
@ -155,7 +155,7 @@ msgid "Please enter a value in range %(min_ram)s - 200."
|
||||||
msgstr "Bitte gib einen Wert von %(min_ram)s bis 200 ein."
|
msgstr "Bitte gib einen Wert von %(min_ram)s bis 200 ein."
|
||||||
|
|
||||||
msgid "VM hosting"
|
msgid "VM hosting"
|
||||||
msgstr ""
|
msgstr "VM Hosting"
|
||||||
|
|
||||||
msgid "month"
|
msgid "month"
|
||||||
msgstr "Monat"
|
msgstr "Monat"
|
||||||
|
|
@ -207,14 +207,14 @@ msgstr ""
|
||||||
|
|
||||||
msgid "Only wants you to pay for what you actually need."
|
msgid "Only wants you to pay for what you actually need."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Möchte, dass du nur bezahlst, was du auch wirklich brauchst: Wähle deine "
|
"Du möchtest nur das bezahlen, was du auch wirklich brauchst: Wähle deine "
|
||||||
"Ressourcen individuell aus!
"
|
"Ressourcen individuell aus!
"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Is creative, using a modern and alternative design for a data center in "
|
"Is creative, using a modern and alternative design for a data center in "
|
||||||
"order to make it more sustainable and affordable at the same time."
|
"order to make it more sustainable and affordable at the same time."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Ist kreativ, indem es sich ein modernes und alternatives Layout zu Nutze "
|
"Es ist kreativ, da es sich ein modernes und alternatives Layout zu Nutze"
|
||||||
"macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu "
|
"macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu "
|
||||||
"können.
"
|
"können.
"
|
||||||
|
|
||||||
|
|
@ -222,9 +222,9 @@ msgid ""
|
||||||
"Cuts down the costs for you by using FOSS (Free Open Source Software) "
|
"Cuts down the costs for you by using FOSS (Free Open Source Software) "
|
||||||
"exclusively, wherefore we can save money from paying licenses."
|
"exclusively, wherefore we can save money from paying licenses."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Sorgt dafür, dass unnötige Kosten erspart werden, indem es ausschliesslich "
|
"Um unnötige Kosten zu sparen werden, wird ausschliesslich Software auf"
|
||||||
"mit FOSS (Free Open Source Software) arbeitet und wir daher auf "
|
"Basis von FOSS (Free Open Source Software) eingesetzt und dadurch können auf "
|
||||||
"Lizenzgebühren verzichten können.
"
|
"Lizenzgebühren verzichtet werden.
"
|
||||||
|
|
||||||
msgid "Scale out"
|
msgid "Scale out"
|
||||||
msgstr "Skalierung"
|
msgstr "Skalierung"
|
||||||
|
|
@ -311,7 +311,7 @@ msgid "Billing Address"
|
||||||
msgstr "Rechnungsadresse"
|
msgstr "Rechnungsadresse"
|
||||||
|
|
||||||
msgid "Make a payment"
|
msgid "Make a payment"
|
||||||
msgstr ""
|
msgstr "Tätige eine Bezahlung"
|
||||||
|
|
||||||
msgid "Your Order"
|
msgid "Your Order"
|
||||||
msgstr "Deine Bestellung"
|
msgstr "Deine Bestellung"
|
||||||
|
|
@ -375,6 +375,9 @@ msgstr "Letzten"
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Typ"
|
msgstr "Typ"
|
||||||
|
|
||||||
|
msgid "Expiry"
|
||||||
|
msgstr "Ablaufdatum"
|
||||||
|
|
||||||
msgid "SELECT"
|
msgid "SELECT"
|
||||||
msgstr "AUSWÄHLEN"
|
msgstr "AUSWÄHLEN"
|
||||||
|
|
||||||
|
|
@ -415,14 +418,23 @@ msgstr "Bestellungsübersicht"
|
||||||
msgid "Product"
|
msgid "Product"
|
||||||
msgstr "Produkt"
|
msgstr "Produkt"
|
||||||
|
|
||||||
|
msgid "Price"
|
||||||
|
msgstr "Preise"
|
||||||
|
|
||||||
|
msgid "VAT for"
|
||||||
|
msgstr "MwSt für"
|
||||||
|
|
||||||
|
msgid "Total Amount"
|
||||||
|
msgstr "Gesamtsumme"
|
||||||
|
|
||||||
msgid "Amount"
|
msgid "Amount"
|
||||||
msgstr ""
|
msgstr "Betrag"
|
||||||
|
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr ""
|
msgstr "Beschreibung"
|
||||||
|
|
||||||
msgid "Recurring"
|
msgid "Recurring"
|
||||||
msgstr ""
|
msgstr "Wiederholend"
|
||||||
|
|
||||||
msgid "Subtotal"
|
msgid "Subtotal"
|
||||||
msgstr "Zwischensumme"
|
msgstr "Zwischensumme"
|
||||||
|
|
@ -434,12 +446,20 @@ msgstr "Mehrwertsteuer"
|
||||||
#| msgid ""
|
#| msgid ""
|
||||||
#| "By clicking \"Place order\" this plan will charge your credit card "
|
#| "By clicking \"Place order\" this plan will charge your credit card "
|
||||||
#| "account with %(total_price)s CHF/month"
|
#| "account with %(total_price)s CHF/month"
|
||||||
|
msgid ""
|
||||||
|
"By clicking \"Place order\" this plan will charge your credit card account "
|
||||||
|
"with %(total_price)s CHF/year"
|
||||||
|
msgstr ""
|
||||||
|
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
|
||||||
|
"CHF pro Jahr belastet"
|
||||||
|
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"By clicking \"Place order\" this plan will charge your credit card account "
|
"By clicking \"Place order\" this plan will charge your credit card account "
|
||||||
"with %(total_price)s CHF/month"
|
"with %(total_price)s CHF/month"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
|
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
|
||||||
"%(vm_total_price)s CHF pro Monat belastet"
|
"CHF pro Monat belastet"
|
||||||
|
|
||||||
#, fuzzy, python-format
|
#, fuzzy, python-format
|
||||||
#| msgid ""
|
#| msgid ""
|
||||||
|
|
@ -470,10 +490,10 @@ msgid "Hold tight, we are processing your request"
|
||||||
msgstr "Bitte warten - wir verarbeiten Deine Anfrage gerade"
|
msgstr "Bitte warten - wir verarbeiten Deine Anfrage gerade"
|
||||||
|
|
||||||
msgid "OK"
|
msgid "OK"
|
||||||
msgstr ""
|
msgstr "Ok"
|
||||||
|
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr ""
|
msgstr "Schliessen"
|
||||||
|
|
||||||
msgid "Some problem encountered. Please try again later."
|
msgid "Some problem encountered. Please try again later."
|
||||||
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
|
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
|
||||||
|
|
@ -485,7 +505,7 @@ msgid "Tech Stack"
|
||||||
msgstr "Tech Stack"
|
msgstr "Tech Stack"
|
||||||
|
|
||||||
msgid "We are seriously open source."
|
msgid "We are seriously open source."
|
||||||
msgstr "Wir sind vollends opensource."
|
msgstr "Wir sind vollends Open Source."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
" Our full software stack is open source – We don't use anything that isn't "
|
" Our full software stack is open source – We don't use anything that isn't "
|
||||||
|
|
@ -555,13 +575,13 @@ msgid "Starting from only 15CHF per month. Try now."
|
||||||
msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!"
|
msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!"
|
||||||
|
|
||||||
msgid "Actions speak louder than words. Let's do it, try our VM now."
|
msgid "Actions speak louder than words. Let's do it, try our VM now."
|
||||||
msgstr "Tagen sagen mehr als Worte – Teste jetzt unsere VM!"
|
msgstr "Taten sagen mehr als Worte – Teste jetzt unsere VM!"
|
||||||
|
|
||||||
msgid "Invalid number of cores"
|
msgid "Invalid number of cores"
|
||||||
msgstr "Ungültige Anzahle CPU-Kerne"
|
msgstr "Ungültige Anzahle CPU-Kerne"
|
||||||
|
|
||||||
msgid "Invalid calculator properties"
|
msgid "Invalid calculator properties"
|
||||||
msgstr ""
|
msgstr "Ungültige Berechnungseigenschaften"
|
||||||
|
|
||||||
msgid "Invalid RAM size"
|
msgid "Invalid RAM size"
|
||||||
msgstr "Ungültige RAM-Grösse"
|
msgstr "Ungültige RAM-Grösse"
|
||||||
|
|
@ -571,7 +591,7 @@ msgstr "Ungültige Speicher-Grösse"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Incorrect pricing name. Please contact support{support_email}"
|
msgid "Incorrect pricing name. Please contact support{support_email}"
|
||||||
msgstr ""
|
msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{user} does not have permission to access the card"
|
msgid "{user} does not have permission to access the card"
|
||||||
|
|
@ -598,11 +618,14 @@ msgid "An error occurred while associating the card. Details: {details}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
|
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
|
||||||
|
|
||||||
msgid "Confirmation of your payment"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid " This is a monthly recurring plan."
|
msgid " This is a monthly recurring plan."
|
||||||
msgstr ""
|
msgstr "Dies ist ein monatlich wiederkehrender Plan."
|
||||||
|
|
||||||
|
msgid " This is an yearly recurring plan."
|
||||||
|
msgstr "Dies ist ein jährlich wiederkehrender Plan."
|
||||||
|
|
||||||
|
msgid "Confirmation of your payment"
|
||||||
|
msgstr "Bestätigung deiner Zahlung"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ""
|
msgid ""
|
||||||
|
|
@ -613,7 +636,8 @@ msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
"Cheers,\n"
|
"Cheers,\n"
|
||||||
"Your Data Center Light team"
|
"Your Data Center Light team"
|
||||||
msgstr ""
|
msgstr "Hallo {name},\n" "\n" "vielen Dank für deine Bestellung!\n" "Wir haben deine Bezahlung in Höhe von {amount:.2f} CHF erhalten. {recurring}\n" "\n" "Grüsse\n"
|
||||||
|
"Dein Data Center Light Team"
|
||||||
|
|
||||||
msgid "Thank you for the payment."
|
msgid "Thank you for the payment."
|
||||||
msgstr "Danke für Deine Bestellung."
|
msgstr "Danke für Deine Bestellung."
|
||||||
|
|
@ -621,7 +645,7 @@ msgstr "Danke für Deine Bestellung."
|
||||||
msgid ""
|
msgid ""
|
||||||
"You will soon receive a confirmation email of the payment. You can always "
|
"You will soon receive a confirmation email of the payment. You can always "
|
||||||
"contact us at info@ungleich.ch for any question that you may have."
|
"contact us at info@ungleich.ch for any question that you may have."
|
||||||
msgstr ""
|
msgstr "Du wirst bald eine Bestätigungs-E-Mail über die Zahlung erhalten. Du kannst jederzeit unter info@ungleich.ch kontaktieren."
|
||||||
|
|
||||||
msgid "Thank you for the order."
|
msgid "Thank you for the order."
|
||||||
msgstr "Danke für Deine Bestellung."
|
msgstr "Danke für Deine Bestellung."
|
||||||
|
|
@ -644,9 +668,6 @@ msgstr ""
|
||||||
#~ msgid "Card Number"
|
#~ msgid "Card Number"
|
||||||
#~ msgstr "Kreditkartennummer"
|
#~ msgstr "Kreditkartennummer"
|
||||||
|
|
||||||
#~ msgid "Expiry Date"
|
|
||||||
#~ msgstr "Ablaufdatum"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "You are not making any payment yet. After placing your order, you will be "
|
#~ "You are not making any payment yet. After placing your order, you will be "
|
||||||
#~ "taken to the Submit Payment Page."
|
#~ "taken to the Submit Payment Page."
|
||||||
|
|
@ -655,9 +676,6 @@ msgstr ""
|
||||||
#~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt "
|
#~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt "
|
||||||
#~ "hast."
|
#~ "hast."
|
||||||
|
|
||||||
#~ msgid "Pricing"
|
|
||||||
#~ msgstr "Preise"
|
|
||||||
|
|
||||||
#~ msgid "Order VM"
|
#~ msgid "Order VM"
|
||||||
#~ msgstr "VM bestellen"
|
#~ msgstr "VM bestellen"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import logging
|
import logging
|
||||||
import oca
|
|
||||||
import sys
|
import sys
|
||||||
import stripe
|
import uuid
|
||||||
|
|
||||||
|
import oca
|
||||||
|
import stripe
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from membership.models import CustomUser, DeletedUser
|
|
||||||
from hosting.models import (
|
from hosting.models import (
|
||||||
HostingOrder, HostingBill, VMDetail, UserCardDetail, UserHostingKey
|
UserCardDetail, UserHostingKey
|
||||||
)
|
)
|
||||||
|
from membership.models import CustomUser, DeletedUser
|
||||||
from opennebula_api.models import OpenNebulaManager
|
from opennebula_api.models import OpenNebulaManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -79,86 +82,6 @@ class Command(BaseCommand):
|
||||||
else:
|
else:
|
||||||
logger.error("Error while deleting the StripeCustomer")
|
logger.error("Error while deleting the StripeCustomer")
|
||||||
|
|
||||||
hosting_orders = HostingOrder.objects.filter(
|
|
||||||
customer=stripe_customer.id
|
|
||||||
)
|
|
||||||
|
|
||||||
vm_ids = []
|
|
||||||
for order in hosting_orders:
|
|
||||||
vm_ids.append(order.vm_id)
|
|
||||||
|
|
||||||
# Delete Billing Address
|
|
||||||
if order.billing_address is not None:
|
|
||||||
logger.debug(
|
|
||||||
"Billing Address {} associated with {} deleted"
|
|
||||||
"".format(order.billing_address.id, email)
|
|
||||||
)
|
|
||||||
order.billing_address.delete()
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
"Error while deleting the billing_address")
|
|
||||||
|
|
||||||
# Delete Order Detail
|
|
||||||
if order.order_detail is not None:
|
|
||||||
logger.debug(
|
|
||||||
"Order Detail {} associated with {} deleted"
|
|
||||||
"".format(order.order_detail.id, email)
|
|
||||||
)
|
|
||||||
order.order_detail.delete()
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
"Error while deleting the order_detail. None")
|
|
||||||
|
|
||||||
# Delete order
|
|
||||||
if order is not None:
|
|
||||||
logger.debug(
|
|
||||||
"Order {} associated with {} deleted"
|
|
||||||
"".format(order.id, email)
|
|
||||||
)
|
|
||||||
order.delete()
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
"Error while deleting the Order")
|
|
||||||
|
|
||||||
hosting_bills = HostingBill.objects.filter(
|
|
||||||
customer=stripe_customer.id
|
|
||||||
)
|
|
||||||
|
|
||||||
# delete hosting bills
|
|
||||||
for bill in hosting_bills:
|
|
||||||
if bill.billing_address is not None:
|
|
||||||
logger.debug(
|
|
||||||
"HostingBills billing address {} associated with {} deleted"
|
|
||||||
"".format(bill.billing_address.id, email)
|
|
||||||
)
|
|
||||||
bill.billing_address.delete()
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
"Error while deleting the HostingBill's Billing address")
|
|
||||||
|
|
||||||
if bill is not None:
|
|
||||||
logger.debug(
|
|
||||||
"HostingBill {} associated with {} deleted"
|
|
||||||
"".format(bill.id, email)
|
|
||||||
)
|
|
||||||
bill.delete()
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
"Error while deleting the HostingBill")
|
|
||||||
|
|
||||||
# delete VMDetail
|
|
||||||
for vm_id in vm_ids:
|
|
||||||
vm_detail = VMDetail.objects.get(vm_id=vm_id)
|
|
||||||
if vm_detail is not None:
|
|
||||||
logger.debug(
|
|
||||||
"vm_detail {} associated with {} deleted"
|
|
||||||
"".format(vm_detail.id, email)
|
|
||||||
)
|
|
||||||
vm_detail.delete()
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
"Error while deleting the vm_detail")
|
|
||||||
|
|
||||||
# delete UserCardDetail
|
# delete UserCardDetail
|
||||||
ucds = UserCardDetail.objects.filter(
|
ucds = UserCardDetail.objects.filter(
|
||||||
stripe_customer=stripe_customer
|
stripe_customer=stripe_customer
|
||||||
|
|
@ -190,8 +113,10 @@ class Command(BaseCommand):
|
||||||
user_id = cus_user.id
|
user_id = cus_user.id
|
||||||
)
|
)
|
||||||
|
|
||||||
# delete CustomUser
|
# reset CustomUser
|
||||||
cus_user.delete()
|
cus_user.email = str(uuid.uuid4())
|
||||||
|
cus_user.validated = 0
|
||||||
|
cus_user.save()
|
||||||
|
|
||||||
# remove user from OpenNebula
|
# remove user from OpenNebula
|
||||||
manager = OpenNebulaManager()
|
manager = OpenNebulaManager()
|
||||||
|
|
|
||||||
134
datacenterlight/management/commands/dumpuser.py
Normal file
134
datacenterlight/management/commands/dumpuser.py
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from membership.models import CustomUser
|
||||||
|
from hosting.models import (
|
||||||
|
HostingOrder, VMDetail, UserCardDetail, UserHostingKey
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '''Dumps the data of a customer into a json file'''
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('customer_email', nargs='+', type=str)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
try:
|
||||||
|
for email in options['customer_email']:
|
||||||
|
try:
|
||||||
|
cus_user = CustomUser.objects.get(email=email)
|
||||||
|
except CustomUser.DoesNotExist as dne:
|
||||||
|
logger.error("CustomUser with email {} does "
|
||||||
|
"not exist".format(email))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
hosting_orders = HostingOrder.objects.filter(
|
||||||
|
customer=cus_user.stripecustomer.id
|
||||||
|
)
|
||||||
|
|
||||||
|
vm_ids = []
|
||||||
|
orders_dict = {}
|
||||||
|
for order in hosting_orders:
|
||||||
|
order_dict = {}
|
||||||
|
vm_ids.append(order.vm_id)
|
||||||
|
order_dict["VM_ID"] = order.vm_id
|
||||||
|
order_dict["Order Nr."] = order.id
|
||||||
|
order_dict["Created On"] = str(order.created_at)
|
||||||
|
order_dict["Price"] = order.price
|
||||||
|
order_dict["Payment card details"] = {
|
||||||
|
"last4": order.last4,
|
||||||
|
"brand": order.cc_brand
|
||||||
|
}
|
||||||
|
if order.subscription_id is not None and order.stripe_charge_id is None:
|
||||||
|
order_dict["Order type"] = "Monthly susbcription"
|
||||||
|
else:
|
||||||
|
order_dict["Order type"] = "One time payment"
|
||||||
|
|
||||||
|
# billing address
|
||||||
|
if order.billing_address is not None:
|
||||||
|
order_dict["Billing Address"] = {
|
||||||
|
"Street": order.billing_address.street_address,
|
||||||
|
"City": order.billing_address.city,
|
||||||
|
"Country": order.billing_address.country,
|
||||||
|
"Postal code": order.billing_address.postal_code,
|
||||||
|
"Card holder name": order.billing_address.cardholder_name
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
"did not find billing_address")
|
||||||
|
|
||||||
|
# Order Detail
|
||||||
|
if order.order_detail is not None:
|
||||||
|
order_dict["Specifications"] = {
|
||||||
|
"RAM": "{} GB".format(order.order_detail.memory),
|
||||||
|
"Cores": order.order_detail.cores,
|
||||||
|
"Disk space (SSD)": "{} GB".format(
|
||||||
|
order.order_detail.ssd_size)
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
"Did not find order_detail. None")
|
||||||
|
|
||||||
|
vm_detail = VMDetail.objects.get(vm_id=order.vm_id)
|
||||||
|
if vm_detail is not None:
|
||||||
|
order_dict["VM Details"] = {
|
||||||
|
"VM_ID": order.vm_id,
|
||||||
|
"IPv4": vm_detail.ipv4,
|
||||||
|
"IPv6": vm_detail.ipv6,
|
||||||
|
"OS": vm_detail.configuration,
|
||||||
|
}
|
||||||
|
order_dict["Terminated on"] = str(vm_detail.terminated_at)
|
||||||
|
|
||||||
|
orders_dict[order.vm_id] = order_dict
|
||||||
|
|
||||||
|
|
||||||
|
# UserCardDetail
|
||||||
|
cards = {}
|
||||||
|
ucds = UserCardDetail.objects.filter(
|
||||||
|
stripe_customer=cus_user.stripecustomer
|
||||||
|
)
|
||||||
|
for ucd in ucds:
|
||||||
|
card = {}
|
||||||
|
if ucd is not None:
|
||||||
|
card["Last 4"] = ucd.last4
|
||||||
|
card["Brand"] = ucd.brand
|
||||||
|
card["Expiry month"] = ucd.exp_month
|
||||||
|
card["Expiry year"] = ucd.exp_year
|
||||||
|
card["Preferred"] = ucd.preferred
|
||||||
|
cards[ucd.id] = card
|
||||||
|
else:
|
||||||
|
logger.error(
|
||||||
|
"Error while deleting the User Card Detail")
|
||||||
|
|
||||||
|
# UserHostingKey
|
||||||
|
keys = {}
|
||||||
|
uhks = UserHostingKey.objects.filter(
|
||||||
|
user=cus_user
|
||||||
|
)
|
||||||
|
for uhk in uhks:
|
||||||
|
key = {
|
||||||
|
"Public key": uhk.public_key,
|
||||||
|
"Name": uhk.name,
|
||||||
|
"Created on": str(uhk.created_at)
|
||||||
|
}
|
||||||
|
if uhk.private_key:
|
||||||
|
key["Private key"] = uhk.private_key
|
||||||
|
keys[uhk.name] = key
|
||||||
|
output_dict = {
|
||||||
|
"User details": {
|
||||||
|
"Name": cus_user.name,
|
||||||
|
"Email": cus_user.email,
|
||||||
|
"Activated": "yes" if cus_user.validated == 1 else "no",
|
||||||
|
"Last login": str(cus_user.last_login)
|
||||||
|
},
|
||||||
|
"Orders": orders_dict,
|
||||||
|
"Payment cards": cards,
|
||||||
|
"SSH Keys": keys
|
||||||
|
}
|
||||||
|
print(json.dumps(output_dict, indent=4))
|
||||||
|
except Exception as e:
|
||||||
|
print(" *** Error occurred. Details {}".format(str(e)))
|
||||||
|
|
@ -55,10 +55,25 @@
|
||||||
</p>
|
</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
|
{% if generic_payment_details.vat_rate > 0 %}
|
||||||
|
<p>
|
||||||
|
<span>{% trans "Price" %}: </span>
|
||||||
|
<strong class="pull-right">CHF {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}}</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span>{% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_rate}}%) : </span>
|
||||||
|
<strong class="pull-right">CHF {{generic_payment_details.vat_amount|floatformat:2|intcomma}}</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span>{% trans "Total Amount" %} : </span>
|
||||||
|
<strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
<p>
|
<p>
|
||||||
<span>{% trans "Amount" %}: </span>
|
<span>{% trans "Amount" %}: </span>
|
||||||
<strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
|
<strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
|
||||||
</p>
|
</p>
|
||||||
|
{% endif %}
|
||||||
{% if generic_payment_details.description %}
|
{% if generic_payment_details.description %}
|
||||||
<p>
|
<p>
|
||||||
<span>{% trans "Description" %}: </span>
|
<span>{% trans "Description" %}: </span>
|
||||||
|
|
@ -139,7 +154,11 @@
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
{% if generic_payment_details %}
|
{% if generic_payment_details %}
|
||||||
{% if generic_payment_details.recurring %}
|
{% if generic_payment_details.recurring %}
|
||||||
|
{% if generic_payment_details.recurring_interval == 'year' %}
|
||||||
|
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/year{% endblocktrans %}.</div>
|
||||||
|
{% else %}
|
||||||
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.</div>
|
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.</div>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.</div>
|
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ from utils.forms import (
|
||||||
BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
|
BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
|
||||||
BillingAddress
|
BillingAddress
|
||||||
)
|
)
|
||||||
from utils.hosting_utils import get_vm_price_with_vat, get_all_public_keys
|
from utils.hosting_utils import (
|
||||||
|
get_vm_price_with_vat, get_all_public_keys, get_vat_rate_for_country
|
||||||
|
)
|
||||||
from utils.stripe_utils import StripeUtils
|
from utils.stripe_utils import StripeUtils
|
||||||
from utils.tasks import send_plain_email_task
|
from utils.tasks import send_plain_email_task
|
||||||
from .cms_models import DCLCalculatorPluginModel
|
from .cms_models import DCLCalculatorPluginModel
|
||||||
|
|
@ -413,10 +415,21 @@ class PaymentOrderView(FormView):
|
||||||
product = generic_payment_form.cleaned_data.get(
|
product = generic_payment_form.cleaned_data.get(
|
||||||
'product_name'
|
'product_name'
|
||||||
)
|
)
|
||||||
|
user_country_vat_rate = get_vat_rate_for_country(
|
||||||
|
address_form.cleaned_data["country"]
|
||||||
|
)
|
||||||
gp_details = {
|
gp_details = {
|
||||||
"product_name": product.product_name,
|
"product_name": product.product_name,
|
||||||
"amount": generic_payment_form.cleaned_data.get(
|
"vat_rate": user_country_vat_rate * 100,
|
||||||
'amount'
|
"vat_amount": round(
|
||||||
|
float(product.product_price) *
|
||||||
|
user_country_vat_rate, 2),
|
||||||
|
"vat_country": address_form.cleaned_data["country"],
|
||||||
|
"amount_before_vat": round(
|
||||||
|
float(product.product_price), 2),
|
||||||
|
"amount": product.get_actual_price(
|
||||||
|
vat_rate=get_vat_rate_for_country(
|
||||||
|
address_form.cleaned_data["country"])
|
||||||
),
|
),
|
||||||
"recurring": generic_payment_form.cleaned_data.get(
|
"recurring": generic_payment_form.cleaned_data.get(
|
||||||
'recurring'
|
'recurring'
|
||||||
|
|
@ -425,7 +438,9 @@ class PaymentOrderView(FormView):
|
||||||
'description'
|
'description'
|
||||||
),
|
),
|
||||||
"product_id": product.id,
|
"product_id": product.id,
|
||||||
"product_slug": product.product_slug
|
"product_slug": product.product_slug,
|
||||||
|
"recurring_interval":
|
||||||
|
product.product_subscription_interval
|
||||||
}
|
}
|
||||||
request.session["generic_payment_details"] = (
|
request.session["generic_payment_details"] = (
|
||||||
gp_details
|
gp_details
|
||||||
|
|
@ -744,6 +759,7 @@ class OrderConfirmationView(DetailView, FormView):
|
||||||
|
|
||||||
if ('generic_payment_type' not in request.session or
|
if ('generic_payment_type' not in request.session or
|
||||||
(request.session['generic_payment_details']['recurring'])):
|
(request.session['generic_payment_details']['recurring'])):
|
||||||
|
recurring_interval = 'month'
|
||||||
if 'generic_payment_details' in request.session:
|
if 'generic_payment_details' in request.session:
|
||||||
amount_to_be_charged = (
|
amount_to_be_charged = (
|
||||||
round(
|
round(
|
||||||
|
|
@ -756,6 +772,10 @@ class OrderConfirmationView(DetailView, FormView):
|
||||||
amount_to_be_charged
|
amount_to_be_charged
|
||||||
)
|
)
|
||||||
stripe_plan_id = plan_name
|
stripe_plan_id = plan_name
|
||||||
|
recurring_interval = request.session['generic_payment_details']['recurring_interval']
|
||||||
|
if recurring_interval == "year":
|
||||||
|
plan_name = "{}-yearly".format(plan_name)
|
||||||
|
stripe_plan_id = plan_name
|
||||||
else:
|
else:
|
||||||
template = request.session.get('template')
|
template = request.session.get('template')
|
||||||
specs = request.session.get('specs')
|
specs = request.session.get('specs')
|
||||||
|
|
@ -782,7 +802,9 @@ class OrderConfirmationView(DetailView, FormView):
|
||||||
stripe_plan = stripe_utils.get_or_create_stripe_plan(
|
stripe_plan = stripe_utils.get_or_create_stripe_plan(
|
||||||
amount=amount_to_be_charged,
|
amount=amount_to_be_charged,
|
||||||
name=plan_name,
|
name=plan_name,
|
||||||
stripe_plan_id=stripe_plan_id)
|
stripe_plan_id=stripe_plan_id,
|
||||||
|
interval=recurring_interval
|
||||||
|
)
|
||||||
subscription_result = stripe_utils.subscribe_customer_to_plan(
|
subscription_result = stripe_utils.subscribe_customer_to_plan(
|
||||||
stripe_api_cus_id,
|
stripe_api_cus_id,
|
||||||
[{"plan": stripe_plan.get(
|
[{"plan": stripe_plan.get(
|
||||||
|
|
@ -977,6 +999,9 @@ class OrderConfirmationView(DetailView, FormView):
|
||||||
'reply_to': [context['email']],
|
'reply_to': [context['email']],
|
||||||
}
|
}
|
||||||
send_plain_email_task.delay(email_data)
|
send_plain_email_task.delay(email_data)
|
||||||
|
recurring_text = _(" This is a monthly recurring plan.")
|
||||||
|
if gp_details['recurring_interval'] == "year":
|
||||||
|
recurring_text = _(" This is an yearly recurring plan.")
|
||||||
|
|
||||||
email_data = {
|
email_data = {
|
||||||
'subject': _("Confirmation of your payment"),
|
'subject': _("Confirmation of your payment"),
|
||||||
|
|
@ -990,7 +1015,7 @@ class OrderConfirmationView(DetailView, FormView):
|
||||||
name=user.get('name'),
|
name=user.get('name'),
|
||||||
amount=gp_details['amount'],
|
amount=gp_details['amount'],
|
||||||
recurring=(
|
recurring=(
|
||||||
_(' This is a monthly recurring plan.')
|
recurring_text
|
||||||
if gp_details['recurring'] else ''
|
if gp_details['recurring'] else ''
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ from django.contrib.auth import authenticate
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from membership.models import CustomUser
|
from membership.models import CustomUser
|
||||||
from utils.hosting_utils import get_all_public_keys
|
|
||||||
from .models import UserHostingKey, GenericProduct
|
from .models import UserHostingKey, GenericProduct
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -110,9 +109,14 @@ class ProductPaymentForm(GenericPaymentForm):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self.product.product_is_subscription:
|
if self.product.product_is_subscription:
|
||||||
|
payment_type = "month"
|
||||||
|
if self.product.product_subscription_interval == "month":
|
||||||
|
payment_type = _('Monthly subscription')
|
||||||
|
elif self.product.product_subscription_interval == "year":
|
||||||
|
payment_type = _('Yearly subscription')
|
||||||
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
||||||
amt=_('Amount in CHF'),
|
amt=_('Amount in CHF'),
|
||||||
payment_type=_('Monthly subscription')
|
payment_type=payment_type
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
||||||
|
|
@ -193,15 +197,6 @@ class UserHostingKeyForm(forms.ModelForm):
|
||||||
KEY_ERROR_MESSAGE = _("Please input a proper SSH key")
|
KEY_ERROR_MESSAGE = _("Please input a proper SSH key")
|
||||||
openssh_pubkey_str = self.data.get('public_key').strip()
|
openssh_pubkey_str = self.data.get('public_key').strip()
|
||||||
|
|
||||||
if openssh_pubkey_str in get_all_public_keys(self.request.user):
|
|
||||||
key_name = UserHostingKey.objects.filter(
|
|
||||||
user_id=self.request.user.id,
|
|
||||||
public_key=openssh_pubkey_str).first().name
|
|
||||||
KEY_EXISTS_MESSAGE = _(
|
|
||||||
"This key exists already with the name \"%(name)s\"") % {
|
|
||||||
'name': key_name}
|
|
||||||
raise forms.ValidationError(KEY_EXISTS_MESSAGE)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file:
|
with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file:
|
||||||
tmp_public_key_file.write(openssh_pubkey_str.encode('utf-8'))
|
tmp_public_key_file.write(openssh_pubkey_str.encode('utf-8'))
|
||||||
tmp_public_key_file.flush()
|
tmp_public_key_file.flush()
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2019-07-09 15:21+0000\n"
|
"POT-Creation-Date: 2019-11-15 16:40+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|
@ -28,28 +28,31 @@ msgid "User does not exist"
|
||||||
msgstr "Der Benutzer existiert nicht"
|
msgstr "Der Benutzer existiert nicht"
|
||||||
|
|
||||||
msgid "Choose a product"
|
msgid "Choose a product"
|
||||||
msgstr ""
|
msgstr "Wähle ein Produkt"
|
||||||
|
|
||||||
msgid "Amount in CHF"
|
msgid "Amount in CHF"
|
||||||
msgstr "Betrag"
|
msgstr "Betrag"
|
||||||
|
|
||||||
msgid "Recurring monthly"
|
msgid "Recurring monthly"
|
||||||
msgstr ""
|
msgstr "monatlich wiederkehrend"
|
||||||
|
|
||||||
msgid "Amount field does not match"
|
msgid "Amount field does not match"
|
||||||
msgstr ""
|
msgstr "Betragsfeld stimmt nicht überein"
|
||||||
|
|
||||||
msgid "Recurring field does not match"
|
msgid "Recurring field does not match"
|
||||||
msgstr ""
|
msgstr "Betragsfeld stimmt nicht überein"
|
||||||
|
|
||||||
msgid "Product name"
|
msgid "Product name"
|
||||||
msgstr "Produkt"
|
msgstr "Produkt"
|
||||||
|
|
||||||
msgid "Monthly subscription"
|
msgid "Monthly subscription"
|
||||||
msgstr ""
|
msgstr "Monatliches Abonnement"
|
||||||
|
|
||||||
|
msgid "Yearly subscription"
|
||||||
|
msgstr "Jährliches Abonnement"
|
||||||
|
|
||||||
msgid "One time payment"
|
msgid "One time payment"
|
||||||
msgstr ""
|
msgstr "Einmalzahlung"
|
||||||
|
|
||||||
msgid "Confirm Password"
|
msgid "Confirm Password"
|
||||||
msgstr "Passwort Bestätigung"
|
msgstr "Passwort Bestätigung"
|
||||||
|
|
@ -72,12 +75,8 @@ msgstr "Key-Name"
|
||||||
msgid "Please input a proper SSH key"
|
msgid "Please input a proper SSH key"
|
||||||
msgstr "Bitte verwende einen gültigen SSH-Key"
|
msgstr "Bitte verwende einen gültigen SSH-Key"
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "This key exists already with the name \"%(name)s\""
|
|
||||||
msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits"
|
|
||||||
|
|
||||||
msgid "Comma not accepted in the name of the key"
|
msgid "Comma not accepted in the name of the key"
|
||||||
msgstr ""
|
msgstr "Komma im Namen des Keys wird nicht akzeptiert"
|
||||||
|
|
||||||
msgid "All Rights Reserved"
|
msgid "All Rights Reserved"
|
||||||
msgstr "Alle Rechte vorbehalten"
|
msgstr "Alle Rechte vorbehalten"
|
||||||
|
|
@ -242,6 +241,10 @@ msgstr ""
|
||||||
msgid "You can view your VM detail by clicking the button below."
|
msgid "You can view your VM detail by clicking the button below."
|
||||||
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
|
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
|
||||||
|
|
||||||
|
msgid "You can log in to your VM by the username <strong>puffy</strong>."
|
||||||
|
msgstr ""
|
||||||
|
"Du kannst Dich auf Deiner VM mit dem user <strong>puffy</strong> einloggen."
|
||||||
|
|
||||||
msgid "View Detail"
|
msgid "View Detail"
|
||||||
msgstr "Details anzeigen"
|
msgstr "Details anzeigen"
|
||||||
|
|
||||||
|
|
@ -255,6 +258,9 @@ msgstr "Deine Bestellung von %(vm_name)s wurde entgegengenommen."
|
||||||
msgid "You can view your VM detail by following the link below."
|
msgid "You can view your VM detail by following the link below."
|
||||||
msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
|
msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
|
||||||
|
|
||||||
|
msgid "You can log in to your VM by the username puffy."
|
||||||
|
msgstr "Du kannst Dich auf Deiner VM mit dem user puffy einloggen."
|
||||||
|
|
||||||
msgid "Password Reset"
|
msgid "Password Reset"
|
||||||
msgstr "Passwort zurücksetzen"
|
msgstr "Passwort zurücksetzen"
|
||||||
|
|
||||||
|
|
@ -398,13 +404,19 @@ msgid "Amount"
|
||||||
msgstr "Betrag"
|
msgstr "Betrag"
|
||||||
|
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr ""
|
msgstr "Beschreibung"
|
||||||
|
|
||||||
msgid "Recurring"
|
msgid "Recurring"
|
||||||
msgstr ""
|
msgstr "wiederkehrend"
|
||||||
|
|
||||||
|
msgid "of"
|
||||||
|
msgstr "von"
|
||||||
|
|
||||||
|
msgid "each year"
|
||||||
|
msgstr "jedes Jahr"
|
||||||
|
|
||||||
msgid "of every month"
|
msgid "of every month"
|
||||||
msgstr ""
|
msgstr "jeden Monat"
|
||||||
|
|
||||||
msgid "BACK TO LIST"
|
msgid "BACK TO LIST"
|
||||||
msgstr "ZURÜCK ZUR LISTE"
|
msgstr "ZURÜCK ZUR LISTE"
|
||||||
|
|
@ -416,16 +428,13 @@ msgid "VM ID"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "IP Address"
|
msgid "IP Address"
|
||||||
msgstr ""
|
msgstr "IP-Adresse"
|
||||||
|
|
||||||
msgid "See Invoice"
|
msgid "See Invoice"
|
||||||
msgstr "Siehe Rechnung"
|
msgstr "Siehe Rechnung"
|
||||||
|
|
||||||
msgid "Page"
|
msgid "Page"
|
||||||
msgstr ""
|
msgstr "Seite"
|
||||||
|
|
||||||
msgid "of"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr "Anmelden"
|
msgstr "Anmelden"
|
||||||
|
|
@ -487,7 +496,7 @@ msgid "Hold tight, we are processing your request"
|
||||||
msgstr "Bitte warten - wir bearbeiten Deine Anfrage gerade"
|
msgstr "Bitte warten - wir bearbeiten Deine Anfrage gerade"
|
||||||
|
|
||||||
msgid "OK"
|
msgid "OK"
|
||||||
msgstr ""
|
msgstr "Ok"
|
||||||
|
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Schliessen"
|
msgstr "Schliessen"
|
||||||
|
|
@ -843,7 +852,7 @@ msgstr "Ungültige Speicher-Grösse"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Incorrect pricing name. Please contact support{support_email}"
|
msgid "Incorrect pricing name. Please contact support{support_email}"
|
||||||
msgstr ""
|
msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"We could not find the requested VM. Please "
|
"We could not find the requested VM. Please "
|
||||||
|
|
@ -862,7 +871,7 @@ msgstr "Fehler beenden VM"
|
||||||
msgid ""
|
msgid ""
|
||||||
"VM terminate action timed out. Please contact support@datacenterlight.ch for "
|
"VM terminate action timed out. Please contact support@datacenterlight.ch for "
|
||||||
"further information."
|
"further information."
|
||||||
msgstr ""
|
msgstr "VM beendet wegen Zeitüberschreitung. Bitte kontaktiere support@datacenterlight.ch für weitere Informationen."
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Virtual Machine %(vm_name)s Cancelled"
|
msgid "Virtual Machine %(vm_name)s Cancelled"
|
||||||
|
|
@ -873,6 +882,10 @@ msgstr ""
|
||||||
"Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es "
|
"Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es "
|
||||||
"noch einmal."
|
"noch einmal."
|
||||||
|
|
||||||
|
#, python-format
|
||||||
|
#~ msgid "This key exists already with the name \"%(name)s\""
|
||||||
|
#~ msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits"
|
||||||
|
|
||||||
#~ msgid "Add your public SSH key"
|
#~ msgid "Add your public SSH key"
|
||||||
#~ msgstr "Füge deinen öffentlichen SSH-Key hinzu"
|
#~ msgstr "Füge deinen öffentlichen SSH-Key hinzu"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
@ -19,6 +20,10 @@ class Command(BaseCommand):
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
try:
|
try:
|
||||||
for email in options['customer_email']:
|
for email in options['customer_email']:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
"---------------------------------------------")
|
||||||
|
)
|
||||||
stripe_utils = StripeUtils()
|
stripe_utils = StripeUtils()
|
||||||
user = CustomUser.objects.get(email=email)
|
user = CustomUser.objects.get(email=email)
|
||||||
if hasattr(user, 'stripecustomer'):
|
if hasattr(user, 'stripecustomer'):
|
||||||
|
|
@ -39,7 +44,9 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
if all_invoices_response['error'] is not None:
|
if all_invoices_response['error'] is not None:
|
||||||
self.stdout.write(self.style.ERROR(all_invoices_response['error']))
|
self.stdout.write(self.style.ERROR(all_invoices_response['error']))
|
||||||
exit(1)
|
user.import_stripe_bill_remark += "{}: {},".format(datetime.datetime.now(), all_invoices_response['error'])
|
||||||
|
user.save()
|
||||||
|
continue
|
||||||
all_invoices = all_invoices_response['response_object']
|
all_invoices = all_invoices_response['response_object']
|
||||||
self.stdout.write(self.style.SUCCESS("Obtained {} invoices".format(len(all_invoices) if all_invoices is not None else 0)))
|
self.stdout.write(self.style.SUCCESS("Obtained {} invoices".format(len(all_invoices) if all_invoices is not None else 0)))
|
||||||
num_invoice_created = 0
|
num_invoice_created = 0
|
||||||
|
|
@ -50,12 +57,25 @@ class Command(BaseCommand):
|
||||||
logger.debug("Invoice %s exists already. Not importing." % invoice['invoice_id'])
|
logger.debug("Invoice %s exists already. Not importing." % invoice['invoice_id'])
|
||||||
except MonthlyHostingBill.DoesNotExist as dne:
|
except MonthlyHostingBill.DoesNotExist as dne:
|
||||||
logger.debug("Invoice id %s does not exist" % invoice['invoice_id'])
|
logger.debug("Invoice id %s does not exist" % invoice['invoice_id'])
|
||||||
num_invoice_created += 1 if MonthlyHostingBill.create(invoice) is not None else logger.error("Did not import invoice for %s" % str(invoice))
|
|
||||||
|
if MonthlyHostingBill.create(invoice) is not None:
|
||||||
|
num_invoice_created += 1
|
||||||
|
else:
|
||||||
|
user.import_stripe_bill_remark += "{}: Import failed - {},".format(
|
||||||
|
datetime.datetime.now(),
|
||||||
|
invoice['invoice_id'])
|
||||||
|
user.save()
|
||||||
|
logger.error("Did not import invoice for %s"
|
||||||
|
"" % str(invoice))
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
self.style.SUCCESS("Number of invoices imported = %s" % num_invoice_created)
|
self.style.SUCCESS("Number of invoices imported = %s" % num_invoice_created)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.stdout.write(self.style.SUCCESS(
|
self.stdout.write(self.style.SUCCESS(
|
||||||
'Customer email %s does not have a stripe customer.' % email))
|
'Customer email %s does not have a stripe customer.' % email))
|
||||||
|
user.import_stripe_bill_remark += "{}: No stripecustomer,".format(
|
||||||
|
datetime.datetime.now()
|
||||||
|
)
|
||||||
|
user.save()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(" *** Error occurred. Details {}".format(str(e)))
|
print(" *** Error occurred. Details {}".format(str(e)))
|
||||||
|
|
|
||||||
44
hosting/management/commands/import_vat_rates.py
Normal file
44
hosting/management/commands/import_vat_rates.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
import csv
|
||||||
|
from hosting.models import VATRates
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '''Imports VAT Rates. Assume vat rates of format https://github.com/kdeldycke/vat-rates/blob/master/vat_rates.csv'''
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('csv_file', nargs='+', type=str)
|
||||||
|
|
||||||
|
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
|
||||||
|
obj, created = VATRates.objects.get_or_create(
|
||||||
|
start_date=row["start_date"],
|
||||||
|
stop_date=row["stop_date"] if row["stop_date"] is not "" 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)))
|
||||||
20
hosting/migrations/0056_auto_20191026_0454.py
Normal file
20
hosting/migrations/0056_auto_20191026_0454.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2019-10-26 04:54
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hosting', '0055_auto_20190701_1614'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hostingbilllineitem',
|
||||||
|
name='amount',
|
||||||
|
field=models.IntegerField(),
|
||||||
|
),
|
||||||
|
]
|
||||||
30
hosting/migrations/0057_vatrates.py
Normal file
30
hosting/migrations/0057_vatrates.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2019-11-15 05:16
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import utils.mixins
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hosting', '0056_auto_20191026_0454'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VATRates',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('start_date', models.DateField(blank=True, null=True)),
|
||||||
|
('stop_date', models.DateField(blank=True, null=True)),
|
||||||
|
('territory_codes', models.TextField(blank=True, default='')),
|
||||||
|
('currency_code', models.CharField(max_length=10)),
|
||||||
|
('rate', models.FloatField()),
|
||||||
|
('rate_type', models.TextField(blank=True, default='')),
|
||||||
|
('description', models.TextField(blank=True, default='')),
|
||||||
|
],
|
||||||
|
bases=(utils.mixins.AssignPermissionsMixin, models.Model),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2019-11-15 14:57
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hosting', '0057_vatrates'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='genericproduct',
|
||||||
|
name='product_subscription_interval',
|
||||||
|
field=models.CharField(default='month', help_text='Choose between `year` and `month`', max_length=10),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -78,13 +78,17 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
|
||||||
product_price = models.DecimalField(max_digits=6, decimal_places=2)
|
product_price = models.DecimalField(max_digits=6, decimal_places=2)
|
||||||
product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0)
|
product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0)
|
||||||
product_is_subscription = models.BooleanField(default=True)
|
product_is_subscription = models.BooleanField(default=True)
|
||||||
|
product_subscription_interval = models.CharField(
|
||||||
|
max_length=10, default="month",
|
||||||
|
help_text="Choose between `year` and `month`")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.product_name
|
return self.product_name
|
||||||
|
|
||||||
def get_actual_price(self):
|
def get_actual_price(self, vat_rate=None):
|
||||||
|
VAT = vat_rate if vat_rate is not None else self.product_vat
|
||||||
return round(
|
return round(
|
||||||
self.product_price + (self.product_price * self.product_vat), 2
|
float(self.product_price) + float(self.product_price) * float(VAT), 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -322,7 +326,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
||||||
logger.debug("Neither subscription id nor vm_id available")
|
logger.debug("Neither subscription id nor vm_id available")
|
||||||
logger.debug("Can't import invoice")
|
logger.debug("Can't import invoice")
|
||||||
return None
|
return None
|
||||||
|
if args['order'] is None:
|
||||||
|
logger.error(
|
||||||
|
"Order is None for {}".format(args['invoice_id']))
|
||||||
|
return None
|
||||||
instance = cls.objects.create(
|
instance = cls.objects.create(
|
||||||
created=datetime.utcfromtimestamp(
|
created=datetime.utcfromtimestamp(
|
||||||
args['created']).replace(tzinfo=pytz.utc),
|
args['created']).replace(tzinfo=pytz.utc),
|
||||||
|
|
@ -340,7 +347,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
||||||
args['period_start']).replace(tzinfo=pytz.utc),
|
args['period_start']).replace(tzinfo=pytz.utc),
|
||||||
period_end=datetime.utcfromtimestamp(
|
period_end=datetime.utcfromtimestamp(
|
||||||
args['period_end']).replace(tzinfo=pytz.utc),
|
args['period_end']).replace(tzinfo=pytz.utc),
|
||||||
billing_reason=args['billing_reason'],
|
billing_reason=(
|
||||||
|
args['billing_reason']
|
||||||
|
if args['billing_reason'] is not None else ''
|
||||||
|
),
|
||||||
discount=args['discount'],
|
discount=args['discount'],
|
||||||
total=args['total'],
|
total=args['total'],
|
||||||
lines_data_count=args['lines_data_count'],
|
lines_data_count=args['lines_data_count'],
|
||||||
|
|
@ -469,7 +479,7 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model):
|
||||||
on_delete=models.CASCADE)
|
on_delete=models.CASCADE)
|
||||||
stripe_plan = models.ForeignKey(StripePlan, null=True,
|
stripe_plan = models.ForeignKey(StripePlan, null=True,
|
||||||
on_delete=models.CASCADE)
|
on_delete=models.CASCADE)
|
||||||
amount = models.PositiveSmallIntegerField()
|
amount = models.IntegerField()
|
||||||
description = models.CharField(max_length=255)
|
description = models.CharField(max_length=255)
|
||||||
discountable = models.BooleanField()
|
discountable = models.BooleanField()
|
||||||
metadata = models.CharField(max_length=128)
|
metadata = models.CharField(max_length=128)
|
||||||
|
|
@ -722,6 +732,16 @@ class UserCardDetail(AssignPermissionsMixin, models.Model):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class VATRates(AssignPermissionsMixin, models.Model):
|
||||||
|
start_date = models.DateField(blank=True, null=True)
|
||||||
|
stop_date = models.DateField(blank=True, null=True)
|
||||||
|
territory_codes = models.TextField(blank=True, default='')
|
||||||
|
currency_code = models.CharField(max_length=10)
|
||||||
|
rate = models.FloatField()
|
||||||
|
rate_type = models.TextField(blank=True, default='')
|
||||||
|
description = models.TextField(blank=True, default='')
|
||||||
|
|
||||||
|
|
||||||
class FailedInvoice(AssignPermissionsMixin, models.Model):
|
class FailedInvoice(AssignPermissionsMixin, models.Model):
|
||||||
permissions = ('view_failedinvoice',)
|
permissions = ('view_failedinvoice',)
|
||||||
stripe_customer = models.ForeignKey(StripeCustomer)
|
stripe_customer = models.ForeignKey(StripeCustomer)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@
|
||||||
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
||||||
{% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %}
|
{% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
{% if 'OpenBSD' in vm_name %}
|
||||||
|
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
||||||
|
{% blocktrans %}You can log in to your VM by the username <strong>puffy</strong>.{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
{% blocktrans %}You have ordered a new virtual machine!{% endblocktrans %}
|
{% blocktrans %}You have ordered a new virtual machine!{% endblocktrans %}
|
||||||
{% blocktrans %}Your order of {{vm_name}} has been charged.{% endblocktrans %}
|
{% blocktrans %}Your order of {{vm_name}} has been charged.{% endblocktrans %}
|
||||||
{% blocktrans %}You can view your VM detail by following the link below.{% endblocktrans %}
|
{% blocktrans %}You can view your VM detail by following the link below.{% endblocktrans %}
|
||||||
|
{% if 'OpenBSD' in vm_name %}
|
||||||
|
{% blocktrans %}You can log in to your VM by the username puffy.{% endblocktrans %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{{ base_url }}{{ order_url }}
|
{{ base_url }}{{ order_url }}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
<label for="configuration">Configuration: </label>
|
<label for="configuration">Configuration: </label>
|
||||||
<select class="form-control" name="vm_template_id" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
|
<select class="form-control" name="vm_template_id" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
|
||||||
{% for template in templates %}
|
{% for template in templates %}
|
||||||
<option value="{{template.id}}">{{ template.name }}</option>
|
<option value="{{template.id}}">{{ template.name|cut:'public-' }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,10 @@
|
||||||
<table>
|
<table>
|
||||||
<tr><th style="width: 35%">Product</th><th style="width: 20%">Period</th><th style="text-align: center; width: 10%">Qty</th><th align="center" style="width: 10%; text-align: center;">Unit Price</th><th style="width: 10%; text-align: right;">Total</th></tr>
|
<tr><th style="width: 35%">Product</th><th style="width: 20%">Period</th><th style="text-align: center; width: 10%">Qty</th><th align="center" style="width: 10%; text-align: center;">Unit Price</th><th style="width: 10%; text-align: right;">Total</th></tr>
|
||||||
{% for line_item in line_items %}
|
{% for line_item in line_items %}
|
||||||
<tr class="border_bottom"><td>{% if line_item.description|length > 0 %}{{line_item.description}}{% elif line_item.stripe_plan.stripe_plan_name|length > 0 %}{{line_item.stripe_plan.stripe_plan_name}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %}</td><td>{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}</td><td align="center">{{line_item.quantity}}</td><td align="center">{{line_item.unit_amount_in_chf}}</td><td align="right">{{line_item.amount_in_chf}}</td></tr>
|
<tr class="border_bottom"><td>{% if line_item.description|length > 0 %}{{line_item.description}}{% elif line_item.stripe_plan.stripe_plan_name|length > 0 %}{{line_item.stripe_plan.stripe_plan_name}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %}</td><td>{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}</td><td align="center">{{line_item.quantity}}</td><td align="center">{{line_item.unit_amount_in_chf}}</td><td align="right">{{line_item.amount_in_chf|floatformat:2}}</td></tr>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr class="grand-total-padding"><td colspan="4">Grand Total</td><td align="right">{{total_in_chf}}</td></tr>
|
<tr class="grand-total-padding"><td colspan="4">Grand Total</td><td align="right">{{total_in_chf|floatformat:2}}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -195,8 +195,13 @@
|
||||||
{% if invoice.order.subscription_id %}
|
{% if invoice.order.subscription_id %}
|
||||||
<p>
|
<p>
|
||||||
<span>{% trans "Recurring" %}: </span>
|
<span>{% trans "Recurring" %}: </span>
|
||||||
|
{% if invoice.order.generic_product.product_subscription_interval == 'year' %}
|
||||||
|
<strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}} {% trans "of" %} {{invoice.order.created_at|date:'b'|title}}
|
||||||
|
{% trans "each year" %}</strong>
|
||||||
|
{% else %}
|
||||||
<strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}}
|
<strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}}
|
||||||
{% trans "of every month" %}</strong>
|
{% trans "of every month" %}</strong>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,13 @@
|
||||||
{% if order.subscription_id %}
|
{% if order.subscription_id %}
|
||||||
<p>
|
<p>
|
||||||
<span>{% trans "Recurring" %}: </span>
|
<span>{% trans "Recurring" %}: </span>
|
||||||
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of every month" %}</strong>
|
{% if order.generic_product.product_subscription_interval == 'year' %}
|
||||||
|
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of" %} {{order.created_at|date:'b'|title}}
|
||||||
|
{% trans "each year" %}</strong>
|
||||||
|
{% else %}
|
||||||
|
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}}
|
||||||
|
{% trans "of every month" %}</strong>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1282,6 +1282,10 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
|
||||||
context['vm']['total_price'] = (
|
context['vm']['total_price'] = (
|
||||||
price + vat - discount['amount']
|
price + vat - discount['amount']
|
||||||
)
|
)
|
||||||
|
except TypeError:
|
||||||
|
logger.error("Type error. Probably we "
|
||||||
|
"came from a generic product. "
|
||||||
|
"Invoice ID %s" % obj.invoice_id)
|
||||||
except WrongIdError:
|
except WrongIdError:
|
||||||
logger.error("WrongIdError while accessing "
|
logger.error("WrongIdError while accessing "
|
||||||
"invoice {}".format(obj.invoice_id))
|
"invoice {}".format(obj.invoice_id))
|
||||||
|
|
@ -1587,6 +1591,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
||||||
|
|
||||||
# Cancel Stripe subscription
|
# Cancel Stripe subscription
|
||||||
stripe_utils = StripeUtils()
|
stripe_utils = StripeUtils()
|
||||||
|
hosting_order = None
|
||||||
try:
|
try:
|
||||||
hosting_order = HostingOrder.objects.get(
|
hosting_order = HostingOrder.objects.get(
|
||||||
vm_id=vm.id
|
vm_id=vm.id
|
||||||
|
|
@ -1648,9 +1653,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
||||||
else:
|
else:
|
||||||
sleep(2)
|
sleep(2)
|
||||||
if not response['status']:
|
if not response['status']:
|
||||||
response['text'] = _("VM terminate action timed out. Please "
|
response['text'] = str(_("VM terminate action timed out. "
|
||||||
"contact support@datacenterlight.ch for "
|
"Please contact "
|
||||||
"further information.")
|
"support@datacenterlight.ch for "
|
||||||
|
"further information."))
|
||||||
context = {
|
context = {
|
||||||
'vm_name': vm_name,
|
'vm_name': vm_name,
|
||||||
'base_url': "{0}://{1}".format(
|
'base_url': "{0}://{1}".format(
|
||||||
|
|
@ -1671,6 +1677,11 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
||||||
email = BaseEmail(**email_data)
|
email = BaseEmail(**email_data)
|
||||||
email.send()
|
email.send()
|
||||||
admin_email_body.update(response)
|
admin_email_body.update(response)
|
||||||
|
admin_email_body["customer_email"] = owner.email
|
||||||
|
admin_email_body["VM_ID"] = vm.id
|
||||||
|
admin_email_body["VM_created_at"] = (str(hosting_order.created_at) if
|
||||||
|
hosting_order is not None
|
||||||
|
else "unknown")
|
||||||
admin_msg_sub = "VM and Subscription for VM {} and user: {}".format(
|
admin_msg_sub = "VM and Subscription for VM {} and user: {}".format(
|
||||||
vm.id,
|
vm.id,
|
||||||
owner.email
|
owner.email
|
||||||
|
|
@ -1760,7 +1771,8 @@ class CheckUserVM(APIView):
|
||||||
response = check_otp(user, realm, token)
|
response = check_otp(user, realm, token)
|
||||||
if response != 200:
|
if response != 200:
|
||||||
return Response('Invalid token', 403)
|
return Response('Invalid token', 403)
|
||||||
manager = OpenNebulaManager()
|
manager = OpenNebulaManager(settings.OPENNEBULA_USERNAME,
|
||||||
|
settings.OPENNEBULA_PASSWORD)
|
||||||
# not the best way to lookup vms by ip
|
# not the best way to lookup vms by ip
|
||||||
# TODO: make this optimal
|
# TODO: make this optimal
|
||||||
vms = manager.get_vms()
|
vms = manager.get_vms()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2019-11-28 07:19
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('membership', '0009_deleteduser'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='import_stripe_bill_remark',
|
||||||
|
field=models.TextField(default='', help_text='Indicates any issues while importing stripe bills'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -82,6 +82,10 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
|
||||||
help_text=_(
|
help_text=_(
|
||||||
'Designates whether the user can log into this admin site.'),
|
'Designates whether the user can log into this admin site.'),
|
||||||
)
|
)
|
||||||
|
import_stripe_bill_remark = models.TextField(
|
||||||
|
default="",
|
||||||
|
help_text="Indicates any issues while importing stripe bills"
|
||||||
|
)
|
||||||
|
|
||||||
objects = MyUserManager()
|
objects = MyUserManager()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import subprocess
|
||||||
from oca.pool import WrongIdError
|
from oca.pool import WrongIdError
|
||||||
|
|
||||||
from datacenterlight.models import VMPricing
|
from datacenterlight.models import VMPricing
|
||||||
from hosting.models import UserHostingKey, VMDetail
|
from hosting.models import UserHostingKey, VMDetail, VATRates
|
||||||
from opennebula_api.serializers import VirtualMachineSerializer
|
from opennebula_api.serializers import VirtualMachineSerializer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -18,7 +18,7 @@ def get_all_public_keys(customer):
|
||||||
:return: A list of public keys
|
:return: A list of public keys
|
||||||
"""
|
"""
|
||||||
return UserHostingKey.objects.filter(user_id=customer.id).values_list(
|
return UserHostingKey.objects.filter(user_id=customer.id).values_list(
|
||||||
"public_key", flat=True)
|
"public_key", flat=True).distinct()
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_vm_detail(user, manager, vm_id):
|
def get_or_create_vm_detail(user, manager, vm_id):
|
||||||
|
|
@ -150,6 +150,20 @@ def ping_ok(host_ipv6):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_vat_rate_for_country(country):
|
||||||
|
vat_rate = None
|
||||||
|
try:
|
||||||
|
vat_rate = VATRates.objects.get(
|
||||||
|
territory_codes=country, start_date__isnull=False, stop_date=None
|
||||||
|
)
|
||||||
|
logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate))
|
||||||
|
return vat_rate.rate
|
||||||
|
except VATRates.DoesNotExist as dne:
|
||||||
|
logger.debug(str(dne))
|
||||||
|
logger.debug("Did not find VAT rate for %s, returning 0" % country)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class HostingUtils:
|
class HostingUtils:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_items_from_list(from_list, items_list):
|
def clear_items_from_list(from_list, items_list):
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,8 @@ class StripeUtils(object):
|
||||||
return charge
|
return charge
|
||||||
|
|
||||||
@handleStripeError
|
@handleStripeError
|
||||||
def get_or_create_stripe_plan(self, amount, name, stripe_plan_id):
|
def get_or_create_stripe_plan(self, amount, name, stripe_plan_id,
|
||||||
|
interval=""):
|
||||||
"""
|
"""
|
||||||
This function checks if a StripePlan with the given
|
This function checks if a StripePlan with the given
|
||||||
stripe_plan_id already exists. If it exists then the function
|
stripe_plan_id already exists. If it exists then the function
|
||||||
|
|
@ -238,6 +239,10 @@ class StripeUtils(object):
|
||||||
:param stripe_plan_id: The id of the Stripe plan to be
|
:param stripe_plan_id: The id of the Stripe plan to be
|
||||||
created. Use get_stripe_plan_id_string function to
|
created. Use get_stripe_plan_id_string function to
|
||||||
obtain the name of the plan to be created
|
obtain the name of the plan to be created
|
||||||
|
:param interval: str representing the interval of the Plan
|
||||||
|
Specifies billing frequency. Either day, week, month or year.
|
||||||
|
Ref: https://stripe.com/docs/api/plans/create#create_plan-interval
|
||||||
|
The default is month
|
||||||
:return: The StripePlan object if it exists else creates a
|
:return: The StripePlan object if it exists else creates a
|
||||||
Plan object in Stripe and a local StripePlan and
|
Plan object in Stripe and a local StripePlan and
|
||||||
returns it. Returns None in case of Stripe error
|
returns it. Returns None in case of Stripe error
|
||||||
|
|
@ -245,6 +250,7 @@ class StripeUtils(object):
|
||||||
_amount = float(amount)
|
_amount = float(amount)
|
||||||
amount = int(_amount * 100) # stripe amount unit, in cents
|
amount = int(_amount * 100) # stripe amount unit, in cents
|
||||||
stripe_plan_db_obj = None
|
stripe_plan_db_obj = None
|
||||||
|
plan_interval = interval if interval is not "" else self.INTERVAL
|
||||||
try:
|
try:
|
||||||
stripe_plan_db_obj = StripePlan.objects.get(
|
stripe_plan_db_obj = StripePlan.objects.get(
|
||||||
stripe_plan_id=stripe_plan_id)
|
stripe_plan_id=stripe_plan_id)
|
||||||
|
|
@ -252,7 +258,7 @@ class StripeUtils(object):
|
||||||
try:
|
try:
|
||||||
self.stripe.Plan.create(
|
self.stripe.Plan.create(
|
||||||
amount=amount,
|
amount=amount,
|
||||||
interval=self.INTERVAL,
|
interval=plan_interval,
|
||||||
name=name,
|
name=name,
|
||||||
currency=self.CURRENCY,
|
currency=self.CURRENCY,
|
||||||
id=stripe_plan_id)
|
id=stripe_plan_id)
|
||||||
|
|
|
||||||
318
vat_rates.csv
Normal file
318
vat_rates.csv
Normal file
|
|
@ -0,0 +1,318 @@
|
||||||
|
start_date,stop_date,territory_codes,currency_code,rate,rate_type,description
|
||||||
|
2011-01-04,,AI,XCD,0,standard,Anguilla (British overseas territory) is exempted of VAT.
|
||||||
|
1984-01-01,,AT,EUR,0.2,standard,Austria (member state) standard VAT rate.
|
||||||
|
1976-01-01,1984-01-01,AT,EUR,0.18,standard,
|
||||||
|
1973-01-01,1976-01-01,AT,EUR,0.16,standard,
|
||||||
|
1984-01-01,,"AT-6691
|
||||||
|
DE-87491",EUR,0.19,standard,Jungholz (Austrian town) special VAT rate.
|
||||||
|
1984-01-01,,"AT-6991
|
||||||
|
AT-6992
|
||||||
|
AT-6993
|
||||||
|
DE-87567
|
||||||
|
DE-87568
|
||||||
|
DE-87569",EUR,0.19,standard,Mittelberg (Austrian town) special VAT rate.
|
||||||
|
1996-01-01,,BE,EUR,0.21,standard,Belgium (member state) standard VAT rate.
|
||||||
|
1994-01-01,1996-01-01,BE,EUR,0.205,standard,
|
||||||
|
1992-04-01,1994-01-01,BE,EUR,0.195,standard,
|
||||||
|
1983-01-01,1992-04-01,BE,EUR,0.19,standard,
|
||||||
|
1981-07-01,1983-01-01,BE,EUR,0.17,standard,
|
||||||
|
1978-07-01,1981-07-01,BE,EUR,0.16,standard,
|
||||||
|
1971-07-01,1978-07-01,BE,EUR,0.18,standard,
|
||||||
|
1999-01-01,,BG,BGN,0.2,standard,Bulgaria (member state) standard VAT rate.
|
||||||
|
1996-07-01,1999-01-01,BG,BGN,0.22,standard,
|
||||||
|
1994-04-01,1996-07-01,BG,BGN,0.18,standard,
|
||||||
|
2011-01-04,,BM,BMD,0,standard,Bermuda (British overseas territory) is exempted of VAT.
|
||||||
|
2014-01-13,,"CY
|
||||||
|
GB-BFPO 57
|
||||||
|
GB-BFPO 58
|
||||||
|
GB-BFPO 59
|
||||||
|
UK-BFPO 57
|
||||||
|
UK-BFPO 58
|
||||||
|
UK-BFPO 59",EUR,0.19,standard,"Cyprus (member state) standard VAT rate.
|
||||||
|
Akrotiri and Dhekelia (British overseas territory) is subjected to Cyprus' standard VAT rate."
|
||||||
|
2013-01-14,2014-01-13,CY,EUR,0.18,standard,
|
||||||
|
2012-03-01,2013-01-14,CY,EUR,0.17,standard,
|
||||||
|
2003-01-01,2012-03-01,CY,EUR,0.15,standard,
|
||||||
|
2002-07-01,2003-01-01,CY,EUR,0.13,standard,
|
||||||
|
2000-07-01,2002-07-01,CY,EUR,0.1,standard,
|
||||||
|
1993-10-01,2000-07-01,CY,EUR,0.08,standard,
|
||||||
|
1992-07-01,1993-10-01,CY,EUR,0.05,standard,
|
||||||
|
2013-01-01,,CZ,CZK,0.21,standard,Czech Republic (member state) standard VAT rate.
|
||||||
|
2010-01-01,2013-01-01,CZ,CZK,0.2,standard,
|
||||||
|
2004-05-01,2010-01-01,CZ,CZK,0.19,standard,
|
||||||
|
1995-01-01,2004-05-01,CZ,CZK,0.22,standard,
|
||||||
|
1993-01-01,1995-01-01,CZ,CZK,0.23,standard,
|
||||||
|
2007-01-01,,DE,EUR,0.19,standard,Germany (member state) standard VAT rate.
|
||||||
|
1998-04-01,2007-01-01,DE,EUR,0.16,standard,
|
||||||
|
1993-01-01,1998-04-01,DE,EUR,0.15,standard,
|
||||||
|
1983-07-01,1993-01-01,DE,EUR,0.14,standard,
|
||||||
|
1979-07-01,1983-07-01,DE,EUR,0.13,standard,
|
||||||
|
1978-01-01,1979-07-01,DE,EUR,0.12,standard,
|
||||||
|
1968-07-01,1978-01-01,DE,EUR,0.11,standard,
|
||||||
|
1968-01-01,1968-07-01,DE,EUR,0.1,standard,
|
||||||
|
2007-01-01,,DE-27498,EUR,0,standard,Heligoland (German island) is exempted of VAT.
|
||||||
|
2007-01-01,,"DE-78266
|
||||||
|
CH-8238",EUR,0,standard,Busingen am Hochrhein (German territory) is exempted of VAT.
|
||||||
|
1992-01-01,,DK,DKK,0.25,standard,Denmark (member state) standard VAT rate.
|
||||||
|
1980-06-30,1992-01-01,DK,DKK,0.22,standard,
|
||||||
|
1978-10-30,1980-06-30,DK,DKK,0.2025,standard,
|
||||||
|
1977-10-03,1978-10-30,DK,DKK,0.18,standard,
|
||||||
|
1970-06-29,1977-10-03,DK,DKK,0.15,standard,
|
||||||
|
1968-04-01,1970-06-29,DK,DKK,0.125,standard,
|
||||||
|
1967-07-03,1968-04-01,DK,DKK,0.1,standard,
|
||||||
|
2009-07-01,,EE,EUR,0.2,standard,Estonia (member state) standard VAT rate.
|
||||||
|
1993-01-01,2009-07-01,EE,EUR,0.18,standard,
|
||||||
|
1991-01-01,1993-01-01,EE,EUR,0.1,standard,
|
||||||
|
2016-06-01,,"GR
|
||||||
|
EL",EUR,0.24,standard,Greece (member state) standard VAT rate.
|
||||||
|
2010-07-01,2016-06-01,"GR
|
||||||
|
EL",EUR,0.23,standard,
|
||||||
|
2010-03-15,2010-07-01,"GR
|
||||||
|
EL",EUR,0.21,standard,
|
||||||
|
2005-04-01,2010-03-15,"GR
|
||||||
|
EL",EUR,0.19,standard,
|
||||||
|
1990-04-28,2005-04-01,"GR
|
||||||
|
EL",EUR,0.18,standard,
|
||||||
|
1988-01-01,1990-04-28,"GR
|
||||||
|
EL",EUR,0.16,standard,
|
||||||
|
1987-01-01,1988-01-01,"GR
|
||||||
|
EL",EUR,0.18,standard,
|
||||||
|
2012-09-01,,ES,EUR,0.21,standard,Spain (member state) standard VAT rate.
|
||||||
|
2010-07-01,2012-09-01,ES,EUR,0.18,standard,
|
||||||
|
1995-01-01,2010-07-01,ES,EUR,0.16,standard,
|
||||||
|
1992-08-01,1995-01-01,ES,EUR,0.15,standard,
|
||||||
|
1992-01-01,1992-08-01,ES,EUR,0.13,standard,
|
||||||
|
1986-01-01,1992-01-01,ES,EUR,0.12,standard,
|
||||||
|
2012-09-01,,"ES-CN
|
||||||
|
ES-GC
|
||||||
|
ES-TF
|
||||||
|
IC",EUR,0,standard,Canary Islands (Spanish autonomous community) is exempted of VAT.
|
||||||
|
2012-09-01,,"ES-ML
|
||||||
|
ES-CE
|
||||||
|
EA",EUR,0,standard,Ceuta and Melilla (Spanish autonomous cities) is exempted of VAT.
|
||||||
|
2013-01-01,,FI,EUR,0.24,standard,Finland (member state) standard VAT rate.
|
||||||
|
2010-07-01,2013-01-01,FI,EUR,0.23,standard,
|
||||||
|
1994-06-01,2010-07-01,FI,EUR,0.22,standard,
|
||||||
|
2013-01-01,,"FI-01
|
||||||
|
AX",EUR,0,standard,Aland Islands (Finish autonomous region) is exempted of VAT.
|
||||||
|
2011-01-04,,FK,FKP,0,standard,Falkland Islands (British overseas territory) is exempted of VAT.
|
||||||
|
1992-01-01,,FO,DKK,0,standard,Faroe Islands (Danish autonomous country) is exempted of VAT.
|
||||||
|
2014-01-01,,"FR
|
||||||
|
MC",EUR,0.2,standard,"France (member state) standard VAT rate.
|
||||||
|
Monaco (sovereign city-state) is member of the EU VAT area and subjected to France's standard VAT rate."
|
||||||
|
2000-04-01,2014-01-01,"FR
|
||||||
|
MC",EUR,0.196,standard,
|
||||||
|
1995-08-01,2000-04-01,"FR
|
||||||
|
MC",EUR,0.206,standard,
|
||||||
|
1982-07-01,1995-08-01,"FR
|
||||||
|
MC",EUR,0.186,standard,
|
||||||
|
1977-01-01,1982-07-01,"FR
|
||||||
|
MC",EUR,0.176,standard,
|
||||||
|
1973-01-01,1977-01-01,"FR
|
||||||
|
MC",EUR,0.2,standard,
|
||||||
|
1970-01-01,1973-01-01,"FR
|
||||||
|
MC",EUR,0.23,standard,
|
||||||
|
1968-12-01,1970-01-01,"FR
|
||||||
|
MC",EUR,0.19,standard,
|
||||||
|
1968-01-01,1968-12-01,"FR
|
||||||
|
MC",EUR,0.1666,standard,
|
||||||
|
2014-01-01,,"FR-BL
|
||||||
|
BL",EUR,0,standard,Saint Barthelemy (French overseas collectivity) is exempted of VAT.
|
||||||
|
2014-01-01,,"FR-GF
|
||||||
|
GF",EUR,0,standard,Guiana (French overseas department) is exempted of VAT.
|
||||||
|
2014-01-01,,"FR-GP
|
||||||
|
GP",EUR,0.085,standard,Guadeloupe (French overseas department) special VAT rate.
|
||||||
|
2014-01-01,,"FR-MF
|
||||||
|
MF",EUR,0,standard,Saint Martin (French overseas collectivity) is subjected to France's standard VAT rate.
|
||||||
|
2014-01-01,,"FR-MQ
|
||||||
|
MQ",EUR,0.085,standard,Martinique (French overseas department) special VAT rate.
|
||||||
|
2014-01-01,,"FR-NC
|
||||||
|
NC",XPF,0,standard,New Caledonia (French special collectivity) is exempted of VAT.
|
||||||
|
2014-01-01,,"FR-PF
|
||||||
|
PF",XPF,0,standard,French Polynesia (French overseas collectivity) is exempted of VAT.
|
||||||
|
2014-01-01,,"FR-PM
|
||||||
|
PM",EUR,0,standard,Saint Pierre and Miquelon (French overseas collectivity) is exempted of VAT.
|
||||||
|
2014-01-01,,"FR-RE
|
||||||
|
RE",EUR,0.085,standard,Reunion (French overseas department) special VAT rate.
|
||||||
|
2014-01-01,,"FR-TF
|
||||||
|
TF",EUR,0,standard,French Southern and Antarctic Lands (French overseas territory) is exempted of VAT.
|
||||||
|
2014-01-01,,"FR-WF
|
||||||
|
WF",XPF,0,standard,Wallis and Futuna (French overseas collectivity) is exempted of VAT.
|
||||||
|
2014-01-01,,"FR-YT
|
||||||
|
YT",EUR,0,standard,Mayotte (French overseas department) is exempted of VAT.
|
||||||
|
2011-01-04,,GG,GBP,0,standard,Guernsey (British Crown dependency) is exempted of VAT.
|
||||||
|
2011-01-04,,GI,GIP,0,standard,Gibraltar (British overseas territory) is exempted of VAT.
|
||||||
|
1992-01-01,,GL,DKK,0,standard,Greenland (Danish autonomous country) is exempted of VAT.
|
||||||
|
2010-07-01,2016-06-01,"GR-34007
|
||||||
|
EL-34007",EUR,0.16,standard,Skyros (Greek island) special VAT rate.
|
||||||
|
2010-07-01,2016-06-01,"GR-37002
|
||||||
|
GR-37003
|
||||||
|
GR-37005
|
||||||
|
EL-37002
|
||||||
|
EL-37003
|
||||||
|
EL-37005",EUR,0.16,standard,Northern Sporades (Greek islands) special VAT rate.
|
||||||
|
2010-07-01,2016-06-01,"GR-64004
|
||||||
|
EL-64004",EUR,0.16,standard,Thasos (Greek island) special VAT rate.
|
||||||
|
2010-07-01,2016-06-01,"GR-68002
|
||||||
|
EL-68002",EUR,0.16,standard,Samothrace (Greek island) special VAT rate.
|
||||||
|
2010-07-01,,"GR-69
|
||||||
|
EL-69",EUR,0,standard,Mount Athos (Greek self-governed part) is exempted of VAT.
|
||||||
|
2010-07-01,2016-06-01,"GR-81
|
||||||
|
EL-81",EUR,0.16,standard,Dodecanese (Greek department) special VAT rate.
|
||||||
|
2010-07-01,2016-06-01,"GR-82
|
||||||
|
EL-82",EUR,0.16,standard,Cyclades (Greek department) special VAT rate.
|
||||||
|
2010-07-01,2016-06-01,"GR-83
|
||||||
|
EL-83",EUR,0.16,standard,Lesbos (Greek department) special VAT rate.
|
||||||
|
2010-07-01,2016-06-01,"GR-84
|
||||||
|
EL-84",EUR,0.16,standard,Samos (Greek department) special VAT rate.
|
||||||
|
2010-07-01,2016-06-01,"GR-85
|
||||||
|
EL-85",EUR,0.16,standard,Chios (Greek department) special VAT rate.
|
||||||
|
2011-01-04,,GS,GBP,0,standard,South Georgia and the South Sandwich Islands (British overseas territory) is exempted of VAT.
|
||||||
|
2012-03-01,,HR,HRK,0.25,standard,Croatia (member state) standard VAT rate.
|
||||||
|
2009-08-01,2012-03-01,HR,HRK,0.23,standard,
|
||||||
|
1998-08-01,2009-08-01,HR,HRK,0.22,standard,
|
||||||
|
2012-01-01,,HU,HUF,0.27,standard,Hungary (member state) standard VAT rate.
|
||||||
|
2009-07-01,2012-01-01,HU,HUF,0.25,standard,
|
||||||
|
2006-01-01,2009-07-01,HU,HUF,0.2,standard,
|
||||||
|
1988-01-01,2006-01-01,HU,HUF,0.25,standard,
|
||||||
|
2012-01-01,,IE,EUR,0.23,standard,Republic of Ireland (member state) standard VAT rate.
|
||||||
|
2010-01-01,2012-01-01,IE,EUR,0.21,standard,
|
||||||
|
2008-12-01,2010-01-01,IE,EUR,0.215,standard,
|
||||||
|
2002-03-01,2008-12-01,IE,EUR,0.21,standard,
|
||||||
|
2001-01-01,2002-03-01,IE,EUR,0.2,standard,
|
||||||
|
1991-03-01,2001-01-01,IE,EUR,0.21,standard,
|
||||||
|
1990-03-01,1991-03-01,IE,EUR,0.23,standard,
|
||||||
|
1986-03-01,1990-03-01,IE,EUR,0.25,standard,
|
||||||
|
1983-05-01,1986-03-01,IE,EUR,0.23,standard,
|
||||||
|
1983-03-01,1983-05-01,IE,EUR,0.35,standard,
|
||||||
|
1982-05-01,1983-03-01,IE,EUR,0.3,standard,
|
||||||
|
1980-05-01,1982-05-01,IE,EUR,0.25,standard,
|
||||||
|
1976-03-01,1980-05-01,IE,EUR,0.2,standard,
|
||||||
|
1973-09-03,1976-03-01,IE,EUR,0.195,standard,
|
||||||
|
1972-11-01,1973-09-03,IE,EUR,0.1637,standard,
|
||||||
|
2011-01-04,,IO,GBP,0,standard,British Indian Ocean Territory (British overseas territory) is exempted of VAT.
|
||||||
|
2013-10-01,,IT,EUR,0.22,standard,Italy (member state) standard VAT rate.
|
||||||
|
2011-09-17,2013-10-01,IT,EUR,0.21,standard,
|
||||||
|
1997-10-01,2011-09-17,IT,EUR,0.2,standard,
|
||||||
|
1988-08-01,1997-10-01,IT,EUR,0.19,standard,
|
||||||
|
1982-08-05,1988-08-01,IT,EUR,0.18,standard,
|
||||||
|
1981-01-01,1982-08-05,IT,EUR,0.15,standard,
|
||||||
|
1980-11-01,1981-01-01,IT,EUR,0.14,standard,
|
||||||
|
1980-07-03,1980-11-01,IT,EUR,0.15,standard,
|
||||||
|
1977-02-08,1980-07-03,IT,EUR,0.14,standard,
|
||||||
|
1973-01-01,1977-02-08,IT,EUR,0.12,standard,
|
||||||
|
2013-10-01,,"IT-22060
|
||||||
|
CH-6911",CHF,0,standard,Campione (Italian town) is exempted of VAT.
|
||||||
|
2013-10-01,,IT-23030,EUR,0,standard,Livigno (Italian town) is exempted of VAT.
|
||||||
|
2011-01-04,,JE,GBP,0,standard,Jersey (British Crown dependency) is exempted of VAT.
|
||||||
|
2011-01-04,,KY,KYD,0,standard,Cayman Islands (British overseas territory) is exempted of VAT.
|
||||||
|
2009-09-01,,LT,EUR,0.21,standard,Lithuania (member state) standard VAT rate.
|
||||||
|
2009-01-01,2009-09-01,LT,EUR,0.19,standard,
|
||||||
|
1994-05-01,2009-01-01,LT,EUR,0.18,standard,
|
||||||
|
2015-01-01,,LU,EUR,0.17,standard,Luxembourg (member state) standard VAT rate.
|
||||||
|
1992-01-01,2015-01-01,LU,EUR,0.15,standard,
|
||||||
|
1983-07-01,1992-01-01,LU,EUR,0.12,standard,
|
||||||
|
1971-01-01,1983-07-01,LU,EUR,0.1,standard,
|
||||||
|
1970-01-01,1971-01-01,LU,EUR,0.8,standard,
|
||||||
|
2012-07-01,,LV,EUR,0.21,standard,Latvia (member state) standard VAT rate.
|
||||||
|
2011-01-01,2012-07-01,LV,EUR,0.22,standard,
|
||||||
|
2009-01-01,2011-01-01,LV,EUR,0.21,standard,
|
||||||
|
1995-05-01,2009-01-01,LV,EUR,0.18,standard,
|
||||||
|
2011-01-04,,MS,XCD,0,standard,Montserrat (British overseas territory) is exempted of VAT.
|
||||||
|
2004-01-01,,MT,EUR,0.18,standard,Malta (member state) standard VAT rate.
|
||||||
|
1995-01-01,2004-01-01,MT,EUR,0.15,standard,
|
||||||
|
2012-10-01,,NL,EUR,0.21,standard,Netherlands (member state) standard VAT rate.
|
||||||
|
2001-01-01,2012-10-01,NL,EUR,0.19,standard,
|
||||||
|
1992-10-01,2001-01-01,NL,EUR,0.175,standard,
|
||||||
|
1989-01-01,1992-10-01,NL,EUR,0.185,standard,
|
||||||
|
1986-10-01,1989-01-01,NL,EUR,0.2,standard,
|
||||||
|
1984-01-01,1986-10-01,NL,EUR,0.19,standard,
|
||||||
|
1976-01-01,1984-01-01,NL,EUR,0.18,standard,
|
||||||
|
1973-01-01,1976-01-01,NL,EUR,0.16,standard,
|
||||||
|
1971-01-01,1973-01-01,NL,EUR,0.14,standard,
|
||||||
|
1969-01-01,1971-01-01,NL,EUR,0.12,standard,
|
||||||
|
2012-10-01,,"NL-AW
|
||||||
|
AW",AWG,0,standard,Aruba (Dutch country) are exempted of VAT.
|
||||||
|
2012-10-01,,"NL-CW
|
||||||
|
NL-SX
|
||||||
|
CW
|
||||||
|
SX",ANG,0,standard,Curacao and Sint Maarten (Dutch countries) are exempted of VAT.
|
||||||
|
2012-10-01,,"NL-BQ1
|
||||||
|
NL-BQ2
|
||||||
|
NL-BQ3
|
||||||
|
BQ
|
||||||
|
BQ-BO
|
||||||
|
BQ-SA
|
||||||
|
BQ-SE",USD,0,standard,"Bonaire, Saba and Sint Eustatius (Dutch special municipalities) are exempted of VAT."
|
||||||
|
2011-01-01,,PL,PLN,0.23,standard,Poland (member state) standard VAT rate.
|
||||||
|
1993-01-08,2011-01-01,PL,PLN,0.22,standard,
|
||||||
|
2011-01-04,,PN,NZD,0,standard,Pitcairn Islands (British overseas territory) is exempted of VAT.
|
||||||
|
2011-01-01,,PT,EUR,0.23,standard,Portugal (member state) standard VAT rate.
|
||||||
|
2010-07-01,2011-01-01,PT,EUR,0.21,standard,
|
||||||
|
2008-07-01,2010-07-01,PT,EUR,0.2,standard,
|
||||||
|
2005-07-01,2008-07-01,PT,EUR,0.21,standard,
|
||||||
|
2002-06-05,2005-07-01,PT,EUR,0.19,standard,
|
||||||
|
1995-01-01,2002-06-05,PT,EUR,0.17,standard,
|
||||||
|
1992-03-24,1995-01-01,PT,EUR,0.16,standard,
|
||||||
|
1988-02-01,1992-03-24,PT,EUR,0.17,standard,
|
||||||
|
1986-01-01,1988-02-01,PT,EUR,0.16,standard,
|
||||||
|
2011-01-01,,PT-20,EUR,0.18,standard,Azores (Portuguese autonomous region) special VAT rate.
|
||||||
|
2011-01-01,,PT-30,EUR,0.22,standard,Madeira (Portuguese autonomous region) special VAT rate.
|
||||||
|
2017-01-01,,RO,RON,0.19,standard,Romania (member state) standard VAT rate.
|
||||||
|
2016-01-01,2017-01-01,RO,RON,0.2,standard,Romania (member state) standard VAT rate.
|
||||||
|
2010-07-01,2016-01-01,RO,RON,0.24,standard,
|
||||||
|
2000-01-01,2010-07-01,RO,RON,0.19,standard,
|
||||||
|
1998-02-01,2000-01-01,RO,RON,0.22,standard,
|
||||||
|
1993-07-01,1998-02-01,RO,RON,0.18,standard,
|
||||||
|
1990-07-01,,SE,SEK,0.25,standard,Sweden (member state) standard VAT rate.
|
||||||
|
1983-01-01,1990-07-01,SE,SEK,0.2346,standard,
|
||||||
|
1981-11-16,1983-01-01,SE,SEK,0.2151,standard,
|
||||||
|
1980-09-08,1981-11-16,SE,SEK,0.2346,standard,
|
||||||
|
1977-06-01,1980-09-08,SE,SEK,0.2063,standard,
|
||||||
|
1971-01-01,1977-06-01,SE,SEK,0.1765,standard,
|
||||||
|
1969-01-01,1971-01-01,SE,SEK,0.1111,standard,
|
||||||
|
2011-01-04,,"AC
|
||||||
|
SH
|
||||||
|
SH-AC
|
||||||
|
SH-HL",SHP,0,standard,Ascension and Saint Helena (British overseas territory) is exempted of VAT.
|
||||||
|
2011-01-04,,"TA
|
||||||
|
SH-TA",GBP,0,standard,Tristan da Cunha (British oversea territory) is exempted of VAT.
|
||||||
|
2013-07-01,,SI,EUR,0.22,standard,Slovenia (member state) standard VAT rate.
|
||||||
|
2002-01-01,2013-07-01,SI,EUR,0.2,standard,
|
||||||
|
1999-07-01,2002-01-01,SI,EUR,0.19,standard,
|
||||||
|
2011-01-01,,SK,EUR,0.2,standard,Slovakia (member state) standard VAT rate.
|
||||||
|
2004-01-01,2011-01-01,SK,EUR,0.19,standard,
|
||||||
|
2003-01-01,2004-01-01,SK,EUR,0.2,standard,
|
||||||
|
1996-01-01,2003-01-01,SK,EUR,0.23,standard,
|
||||||
|
1993-08-01,1996-01-01,SK,EUR,0.25,standard,
|
||||||
|
1993-01-01,1993-08-01,SK,EUR,0.23,standard,
|
||||||
|
2011-01-04,,TC,USD,0,standard,Turks and Caicos Islands (British overseas territory) is exempted of VAT.
|
||||||
|
2011-01-04,,"GB
|
||||||
|
UK
|
||||||
|
IM",GBP,0.2,standard,"United Kingdom (member state) standard VAT rate.
|
||||||
|
Isle of Man (British self-governing dependency) is member of the EU VAT area and subjected to UK's standard VAT rate."
|
||||||
|
2010-01-01,2011-01-04,"GB
|
||||||
|
UK
|
||||||
|
IM",GBP,0.175,standard,
|
||||||
|
2008-12-01,2010-01-01,"GB
|
||||||
|
UK
|
||||||
|
IM",GBP,0.15,standard,
|
||||||
|
1991-04-01,2008-12-01,"GB
|
||||||
|
UK
|
||||||
|
IM",GBP,0.175,standard,
|
||||||
|
1979-06-18,1991-04-01,"GB
|
||||||
|
UK
|
||||||
|
IM",GBP,0.15,standard,
|
||||||
|
1974-07-29,1979-06-18,"GB
|
||||||
|
UK
|
||||||
|
IM",GBP,0.08,standard,
|
||||||
|
1973-04-01,1974-07-29,"GB
|
||||||
|
UK
|
||||||
|
IM",GBP,0.1,standard,
|
||||||
|
2011-01-04,,VG,USD,0,standard,British Virgin Islands (British overseas territory) is exempted of VAT.
|
||||||
|
2014-01-01,,CP,EUR,0,standard,Clipperton Island (French overseas possession) is exempted of VAT.
|
||||||
|
2019-11-15,,CH,CHF,0.077,standard,Switzerland standard VAT (added manually)
|
||||||
|
2019-11-15,,MC,EUR,0.196,standard,Monaco standard VAT (added manually)
|
||||||
|
2019-11-15,,FR,EUR,0.2,standard,France standard VAT (added manually)
|
||||||
|
2019-11-15,,GR,EUR,0.24,standard,Greece standard VAT (added manually)
|
||||||
|
2019-11-15,,GB,EUR,0.2,standard,UK standard VAT (added manually)
|
||||||
|
Loading…
Add table
Add a link
Reference in a new issue