VBV-705 feat: cron job abacus export

This commit is contained in:
Daniel Egger 2024-08-30 15:12:43 +02:00
parent 02b08cf3a8
commit 9f81cb2db3
11 changed files with 288 additions and 59 deletions

View File

@ -4,8 +4,11 @@
# Run every 6 hours
0 */6 * * * /usr/local/bin/python /app/manage.py simple_dummy_job
# Run every hour
0 */1 * * * /usr/local/bin/python /app/manage.py edoniq_import_results
# Run every hour at minute 17
17 * * * * /usr/local/bin/python /app/manage.py edoniq_import_results
# Run every hour at minute 43
43 * * * * /usr/local/bin/python /app/manage.py abacus_export_job
# every day at 19:30
30 19 * * * /usr/local/bin/python /app/manage.py send_email_reminders --type=all

View File

@ -1,13 +1,14 @@
import os
import shutil
import tempfile
from datetime import datetime
from datetime import date
from io import StringIO
from subprocess import Popen
from time import sleep
import pytest
from django.conf import settings
from freezegun import freeze_time
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.core.create_default_users import create_default_users
@ -70,6 +71,8 @@ def test_upload_abacus_xml(setup_abacus_env):
)
feuz = User.objects.get(username="andreas.feuz@eiger-versicherungen.ch")
with freeze_time("2024-02-15 08:33:12"):
feuz_checkout_info = CheckoutInformationFactory(
user=feuz,
transaction_id="24021508331287484",
@ -88,10 +91,19 @@ def test_upload_abacus_xml(setup_abacus_env):
organisation_city="Bern",
organisation_country_id="CH",
)
feuz_checkout_info.created_at = datetime(2024, 2, 15, 8, 33, 12, 0)
with freeze_time("2024-02-15 09:37:41"):
abacus_ssh_upload(feuz_checkout_info)
# check transmission data
feuz_checkout_info.refresh_from_db()
assert feuz_checkout_info.invoice_transmitted_at.date() == date(2024, 2, 15)
abacus_transmission_list = feuz_checkout_info.additional_json_data.get(
"abacus_transmission_list", []
)
assert len(abacus_transmission_list) == 1
assert abacus_transmission_list[0]["transmitted_at"][0:10] == "2024-02-15"
# check if files got created
debitor_filepath = os.path.join(tmppath, "debitor/myVBV_debi_60000012.xml")
assert os.path.exists(debitor_filepath)
@ -129,3 +141,41 @@ def test_upload_abacus_xml(setup_abacus_env):
tmppath, "order/myVBV_orde_60000012_20240215083312_6000000124.xml"
)
assert not os.path.exists(order_filepath)
with freeze_time("2024-04-21 17:33:19"):
# change customer data later will retrigger and redo abacus upload
feuz_checkout_info.first_name = "Peter"
feuz_checkout_info.last_name = "Egger"
feuz_checkout_info.save()
abacus_ssh_upload(feuz_checkout_info)
# check transmission data
feuz_checkout_info.refresh_from_db()
assert feuz_checkout_info.invoice_transmitted_at.date() == date(2024, 4, 21)
abacus_transmission_list = feuz_checkout_info.additional_json_data.get(
"abacus_transmission_list", []
)
assert len(abacus_transmission_list) == 2
assert abacus_transmission_list[1]["transmitted_at"][0:10] == "2024-04-21"
# check if files got created
debitor_filepath = os.path.join(tmppath, "debitor/myVBV_debi_60000012.xml")
assert os.path.exists(debitor_filepath)
with open(debitor_filepath) as debitor_file:
debi_content = debitor_file.read()
assert "<CustomerNumber>60000012</CustomerNumber>" in debi_content
assert "<Email>andreas.feuz@eiger-versicherungen.ch</Email>" in debi_content
order_filepath = os.path.join(
tmppath, "order/myVBV_orde_20240215083312_60000012_6000000124.xml"
)
assert os.path.exists(order_filepath)
with open(order_filepath) as order_file:
order_content = order_file.read()
assert (
"<ReferencePurchaseOrder>24021508331287484</ReferencePurchaseOrder>"
in order_content
)
assert "<CustomerNumber>60000012</CustomerNumber>" in order_content

View File

@ -64,6 +64,8 @@ class CheckoutInformationAdmin(admin.ModelAdmin):
list_display = (
"product_sku",
customer,
"first_name",
"last_name",
"product_name",
"product_price",
"created_at",
@ -78,7 +80,11 @@ class CheckoutInformationAdmin(admin.ModelAdmin):
"user__first_name",
"user__last_name",
"user__username",
"first_name",
"last_name",
"transaction_id",
"refno2",
"organisation_detail_name",
"abacus_order_id",
"user__abacus_debitor_number",
]
@ -91,6 +97,14 @@ class CheckoutInformationAdmin(admin.ModelAdmin):
"state",
"product_price",
"webhook_history",
"refno2",
"product_sku",
"invoice_transmitted_at",
"cembra_byjuno_invoice",
"ip_address",
"device_fingerprint_session_key",
"abacus_order_id",
"abacus_ssh_upload_done",
]

View File

@ -4,6 +4,8 @@ from xml.dom import minidom
from xml.etree.ElementTree import Element, SubElement, tostring
import structlog
from django.db.models import F, Q
from django.utils import timezone
from vbv_lernwelt.shop.const import VV_PRODUCT_NUMBER
from vbv_lernwelt.shop.invoice.abacus_sftp_client import AbacusSftpClient
@ -12,14 +14,34 @@ from vbv_lernwelt.shop.models import CheckoutInformation, CheckoutState
logger = structlog.get_logger(__name__)
def filter_checkout_information_for_abacus_upload_qs():
return CheckoutInformation.objects.filter(
Q(abacus_ssh_upload_done=False)
| Q(updated_at__gt=F("invoice_transmitted_at") + datetime.timedelta(minutes=1)),
state=CheckoutState.PAID,
)
def abacus_ssh_upload(checkout_information: CheckoutInformation):
if checkout_information.state != CheckoutState.PAID:
# only upload invoice if checkout is paid
return True
try:
if not checkout_information.abacus_ssh_upload_done:
# only upload data for not yet uploaded invoices
if (
# invoice not in abacus yet
not checkout_information.abacus_ssh_upload_done
# invoice updated more than 5 minutes ago
or (
checkout_information.abacus_ssh_upload_done
and checkout_information.invoice_transmitted_at is not None
and (
checkout_information.updated_at
- checkout_information.invoice_transmitted_at
)
> datetime.timedelta(minutes=1)
)
):
invoice_xml_filename, invoice_xml_content = create_invoice_xml(
checkout_information
)
@ -35,7 +57,22 @@ def abacus_ssh_upload(checkout_information: CheckoutInformation):
)
checkout_information.abacus_ssh_upload_done = True
checkout_information.invoice_transmitted_at = datetime.datetime.now()
checkout_information.invoice_transmitted_at = timezone.now()
abacus_transmission_list = checkout_information.additional_json_data.get(
"abacus_transmission_list", []
)
abacus_transmission_list.append(
{
"invoice_filename": invoice_xml_filename,
"customer_filename": customer_xml_filename,
"transmitted_at": checkout_information.invoice_transmitted_at.isoformat(),
}
)
checkout_information.additional_json_data["abacus_transmission_list"] = (
abacus_transmission_list
)
checkout_information.save()
return True
@ -45,6 +82,7 @@ def abacus_ssh_upload(checkout_information: CheckoutInformation):
checkout_information_id=checkout_information.id,
exception=str(e),
exc_info=True,
label="abacus_ssh_upload",
)
return False

View File

@ -0,0 +1,45 @@
import structlog
from vbv_lernwelt.core.base import LoggedCommand
from vbv_lernwelt.shop.invoice.abacus import (
abacus_ssh_upload,
filter_checkout_information_for_abacus_upload_qs,
)
logger = structlog.get_logger(__name__)
class Command(LoggedCommand):
help = "Export `CheckoutInformation` invoices to Abacus"
def handle(self, *args, **options):
logger.info(
"abacus_export_job started",
label="abacus_ssh_upload",
)
stat_results = {
"num_invoices": 0,
"num_uploaded": 0,
"num_error": 0,
}
abacus_ci_qs = filter_checkout_information_for_abacus_upload_qs()
stat_results["num_invoices"] = abacus_ci_qs.count()
for checkout_info in abacus_ci_qs:
abacus_ssh_upload_result = abacus_ssh_upload(checkout_info)
if abacus_ssh_upload_result:
stat_results["num_uploaded"] += 1
else:
stat_results["num_error"] += 1
self.job_log.json_data = stat_results
self.job_log.save()
logger.info(
"abacus_export_job finished",
label="abacus_ssh_upload",
stat_results=stat_results,
)

View File

@ -0,0 +1,17 @@
# Generated by Django 4.2.13 on 2024-08-30 13:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("shop", "0017_checkoutinformation_chosen_profile"),
]
operations = [
migrations.AddField(
model_name="checkoutinformation",
name="additional_json_data",
field=models.JSONField(blank=True, default=dict),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 4.2.13 on 2024-08-30 13:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("shop", "0018_checkoutinformation_additional_json_data"),
]
operations = [
migrations.AlterField(
model_name="checkoutinformation",
name="refno2",
field=models.CharField(blank=True, default="", max_length=255),
),
]

View File

@ -62,7 +62,7 @@ class CheckoutInformation(models.Model):
invoice_transmitted_at = models.DateTimeField(blank=True, null=True)
transaction_id = models.CharField(max_length=255)
refno2 = models.CharField(max_length=255)
refno2 = models.CharField(max_length=255, blank=True, default="")
# end user (required)
first_name = models.CharField(max_length=255)
@ -116,6 +116,8 @@ class CheckoutInformation(models.Model):
abacus_order_id = models.BigIntegerField(unique=True, null=True, blank=True)
abacus_ssh_upload_done = models.BooleanField(default=False)
additional_json_data = models.JSONField(default=dict, blank=True)
def set_increment_abacus_order_id(self):
if self.abacus_order_id:
return self

View File

@ -1,6 +1,8 @@
from datetime import date, datetime
from django.test import TestCase
from django.utils import timezone
from freezegun import freeze_time
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.core.create_default_users import create_default_users
@ -8,9 +10,11 @@ from vbv_lernwelt.core.model_utils import add_countries
from vbv_lernwelt.shop.invoice.abacus import (
create_customer_xml,
create_invoice_xml,
filter_checkout_information_for_abacus_upload_qs,
render_customer_xml,
render_invoice_xml,
)
from vbv_lernwelt.shop.models import CheckoutState
from vbv_lernwelt.shop.tests.factories import CheckoutInformationFactory
USER_USERNAME = "testuser"
@ -23,6 +27,44 @@ class AbacusInvoiceTestCase(TestCase):
add_countries(small_set=True)
create_default_users()
def test_filter_checkout_information_for_abacus_upload(self):
feuz = User.objects.get(username="andreas.feuz@eiger-versicherungen.ch")
with freeze_time("2024-02-15 08:33:12"):
feuz_checkout_info = CheckoutInformationFactory(
user=feuz,
transaction_id="24021508331287484",
first_name="Andreas",
last_name="Feuz",
street="Eggersmatt",
street_number="32",
postal_code="1719",
city="Zumholz",
country_id="CH",
invoice_address="prv",
state=CheckoutState.ONGOING,
)
qs = filter_checkout_information_for_abacus_upload_qs()
self.assertEqual(qs.count(), 0)
feuz_checkout_info.state = CheckoutState.PAID
feuz_checkout_info.save()
qs = filter_checkout_information_for_abacus_upload_qs()
self.assertEqual(qs.count(), 1)
feuz_checkout_info.abacus_ssh_upload_done = True
feuz_checkout_info.invoice_transmitted_at = timezone.now()
feuz_checkout_info.save()
qs = filter_checkout_information_for_abacus_upload_qs()
self.assertEqual(qs.count(), 0)
with freeze_time("2024-02-15 09:44:44"):
feuz_checkout_info.first_name = "Peter"
feuz_checkout_info.save()
qs = filter_checkout_information_for_abacus_upload_qs()
self.assertEqual(qs.count(), 1)
def test_create_invoice_xml(self):
# set abacus_number before
_pat = User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch")

View File

@ -167,6 +167,7 @@ def checkout_vv(request):
checkout_info.set_increment_abacus_order_id()
refno2 = f"{request.user.abacus_debitor_number}_{VV_PRODUCT_NUMBER}"
checkout_info.refno2 = refno2
try:
datatrans_customer_data = None