From 17405c8c5dd8eebf4fb64261a2035d61c04f4f86 Mon Sep 17 00:00:00 2001 From: Frank Schubert Date: Mon, 31 Mar 2025 13:50:49 +0200 Subject: [PATCH] PreorderBilling --- .../rml/invoice-email-anb.php | 22 +++ Layout/default/PreorderBilling/Index.php | 9 +- .../default/PreorderBillingInvoice/Index.php | 130 +++++++++++++- .../PreorderBillingInvoice/PDF_MAIN-4807.php | 19 +- Layout/default/Preordercampaign/Form.php | 16 ++ .../PreorderBilling/PreorderBilling.php | 9 +- .../PreorderBillingController.php | 87 +++++++++- .../PreorderBillingInvoice.php | 95 +++++++++- .../PreorderBillingInvoiceController.php | 164 +++++++++++++++++- .../PreorderHistory/PreorderHistoryModel.php | 9 +- .../PreordercampaignController.php | 1 + .../PreordercampaignModel.php | 1 + ...07_preordercampaign_add_fibu_cost_code.php | 31 ++++ ...250328142831_preorder_billing_add_fibu.php | 35 ++++ ...0328181907_preorder_billing_add_extref.php | 33 ++++ scripts/invoice/invoice-job-broker.php | 12 +- .../send-preorder-invoice-email.php | 74 ++++++++ 17 files changed, 704 insertions(+), 43 deletions(-) create mode 100644 Layout/default/Emailtemplates/preorder-invoice/rml/invoice-email-anb.php create mode 100644 db/migrations/20250328140707_preordercampaign_add_fibu_cost_code.php create mode 100644 db/migrations/20250328142831_preorder_billing_add_fibu.php create mode 100644 db/migrations/20250328181907_preorder_billing_add_extref.php create mode 100644 scripts/invoice/job-runners/send-preorder-invoice-email.php diff --git a/Layout/default/Emailtemplates/preorder-invoice/rml/invoice-email-anb.php b/Layout/default/Emailtemplates/preorder-invoice/rml/invoice-email-anb.php new file mode 100644 index 000000000..d5438fefc --- /dev/null +++ b/Layout/default/Emailtemplates/preorder-invoice/rml/invoice-email-anb.php @@ -0,0 +1,22 @@ +setReturnValue([ + 'subject' => "Ihre ".(($invoice->total < 0) ? "Gutschrift" : "Rechnung" )." ".$invoice->invoice_number, + 'from_email' => "no-reply@rmlinfrastruktur.at", + 'from_email_name' => "Verrechnung | RML Infrastruktur GmbH" +]); +?> +Sehr geehrte Damen und Herren, + +im Anhang übersenden wir Ihnen unsere Abrechnung invoice_date))->modify("-1 month")?> +Im Anhang erhalten Sie Ihre aktuelle total < 0) ? "Gutschrift" : "Rechnung"?>. + +Beste Grüße, + +RML Infrastruktur GmbH +Wirtschaftspark A | 8940 Liezen ++43 664 128 10 40 +rechnung@rmlinfrastruktur.at | www.rmlinfrastruktur.at +www.facebook.com/rmlinfrastruktur diff --git a/Layout/default/PreorderBilling/Index.php b/Layout/default/PreorderBilling/Index.php index 14bda6ece..64aa5eb97 100644 --- a/Layout/default/PreorderBilling/Index.php +++ b/Layout/default/PreorderBilling/Index.php @@ -55,7 +55,7 @@ $pagination_entity_name = "Billingrecords";
- + "/>
@@ -156,7 +156,7 @@ $pagination_entity_name = "Billingrecords"; Preis Preis Setup Zustellung - + Erstellt @@ -186,7 +186,10 @@ $pagination_entity_name = "Billingrecords"; ">€ price,2,",",".")?> ">€ price_setup,2,",",".")?> billing_delivery == "email") ? "Email" : "Papier"?> - + + creator->name?>
+ create)?> + diff --git a/Layout/default/PreorderBillingInvoice/Index.php b/Layout/default/PreorderBillingInvoice/Index.php index 6d6d619ad..f8351b483 100644 --- a/Layout/default/PreorderBillingInvoice/Index.php +++ b/Layout/default/PreorderBillingInvoice/Index.php @@ -60,11 +60,11 @@ $pagination_entity_name = "Rechnungen";
- + "/>
- + "/>
@@ -91,6 +91,52 @@ $pagination_entity_name = "Rechnungen";
+
+
+
+
+

Rechungsemails versenden

+
"> + +
+
+ + +
+
+ + + + +
Aktuelle Email Jobs
+ + + + + + + + + + + + + + + +
Start abGestartetBeendetStatus
from_date?>started?>finished?> + status == "timeout" ? "text-danger" : "text-info"))?>">status) ? $job->status : "neu"?>
+ getResult("sent") ? $job->getResult("sent")." Emails versendet" : ""?> +
+ +
+
+
+ + +
+
+

Rechnungen

@@ -113,7 +159,8 @@ $pagination_entity_name = "Rechnungen"; Netto Ust. Brutto - + Zustellung + Erstellt @@ -141,7 +188,11 @@ $pagination_entity_name = "Rechnungen"; € total, 2, ",", ".")?> € total_gross - $invoice->total, 2, ",", ".")?> € total_gross, 2, ",", ".")?> - + date_delivered) ? " ".date("d.m.Y H:i", $invoice->date_delivered) : ""?> + + creator->name?>
+ create)?> + @@ -179,5 +230,76 @@ $pagination_entity_name = "Rechnungen"; todayBtn: 'linked', autoclose: true }); + $('.datepicker').datepicker({ + orientation: "bottom", + language: 'de', + format: "dd.mm.yyyy", + showWeekDays: true, + todayBtn: 'linked', + autoclose: true + }); + + var status_update; + + function updateStatus() { + $.post( + "", + { + do: "getActiveJobs", + type: "send-preorder-invoice-email" + }, + function(success) { + if(success.status != "OK") return; + + jobs = success.result.jobs; + jobs.forEach((job) => { + let status = JSON.parse(job.result); + if(!status) return; + + let status_text = ""; + let count = 0; + + if(job.task == "send-preorder-invoice-email") { + status_text = status.sent + " Emails versendet"; + count = status.sent; + } + + let old_count = 0; + let m = $("#job-" + job.id + " .status-text").text().match(/^(\d+)/); + if(m) { + old_count = m[1]; + } + if(old_count != count) { + $("#job-" + job.id + " .status-text").fadeOut(); + $("#job-" + job.id + " .status-text").promise().done(() => {$("#job-" + job.id + " .status-text").text(status_text).fadeIn()}); + } + + $("#job-" + job.id + " .status").text(job.status ? job.status : "neu"); + if(job.status == "timeout") { + $("#job-" + job.id + " .status").removeClass("text-info text-success").addClass("text-danger"); + } else if(job.status == "finished") { + $("#job-" + job.id + " .status").removeClass("text-info text-danger").addClass("text-success"); + } else { + $("#job-" + job.id + " .status").removeClass("text-danger text-success").addClass("text-info"); + } + + if(job.finished) { + $("#job-" + job.id + " .finished").text(job.finished); + } else { + $("#job-" + job.id + " .finished").text(""); + } + }); + + status_update = setTimeout(updateStatus, 2000); + }, + "json" + ); + } + + $(document).ready(() => { + + status_update = setTimeout(updateStatus, 1000); + + }); \ No newline at end of file diff --git a/Layout/default/PreorderBillingInvoice/PDF_MAIN-4807.php b/Layout/default/PreorderBillingInvoice/PDF_MAIN-4807.php index 818533e51..3f7f324ba 100644 --- a/Layout/default/PreorderBillingInvoice/PDF_MAIN-4807.php +++ b/Layout/default/PreorderBillingInvoice/PDF_MAIN-4807.php @@ -76,7 +76,7 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]); } tr.position td:first-child { - vertical-align: middle !important; + /*vertical-align: middle !important;*/ padding-left: 4pt; } @@ -119,9 +119,14 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]); ?> "> - + article_number?> - article_name?> + + article_name?> + article_info): ?> +
article_info?>
+ + unit?> € @@ -165,7 +170,7 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]); Zahlungskondition: Zahlbar sofort nach Erhalt der Rechnung ohne Abzug - QR-Code + QR-Code Bankverbindung: @@ -174,10 +179,10 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]); IBAN: AT85 1200 0100 3986 5885   BIC: BKAUATWWXXX - + Kontakt: - +43 664 128 10 43
- office@rml-infrastruktur.at + +43 664 128 1040
+ rechnung@rmlinfrastruktur.at diff --git a/Layout/default/Preordercampaign/Form.php b/Layout/default/Preordercampaign/Form.php index 8db2ea694..0288a27fa 100644 --- a/Layout/default/Preordercampaign/Form.php +++ b/Layout/default/Preordercampaign/Form.php @@ -379,6 +379,22 @@
+
+
+

Verrechnungsdaten

+ +
+ +
+ +
+ + +
+ +
+
+

Emailbenachrichtigungen

diff --git a/application/PreorderBilling/PreorderBilling.php b/application/PreorderBilling/PreorderBilling.php index 98f487403..d70147dde 100644 --- a/application/PreorderBilling/PreorderBilling.php +++ b/application/PreorderBilling/PreorderBilling.php @@ -5,6 +5,8 @@ class PreorderBilling extends mfBaseModel { private $preorder; private $invoice; private $adb_wohneinheit; + private $creator; + private $editor; public static $earliest_bill_date = "2025-01-01"; @@ -84,9 +86,10 @@ class PreorderBilling extends mfBaseModel { $model = new PreorderBilling(); $table_fields = [ - "netowner_id","invoice_id", "preorder_id", "product_type", "oaid", "adb_wohneinheit_id", "order_date", "start_date", "end_date", "preorderbillingcustomer_id", - "owner_id", "billingaddress_id", "fibu_account_number", "company", "firstname", "lastname", "street", "zip", "city", "country", "email", "uid", "billing_delivery", - "product_id", "product_type", "product_name", "product_info", "article_number", "amount", "unit", "price", "price_setup", "vatrate", "billing_period", + "netowner_id","invoice_id", "preorder_id", "product_type", "oaid", "adb_wohneinheit_id", "extref", "order_date", "start_date", "end_date", "preorderbillingcustomer_id", + "owner_id", "billingaddress_id", "fibu_account_number", "fibu_cost_account", "fibu_revenue_account", "company", "firstname", "lastname", "street", "zip", "city", + "country", "email", "uid", "billing_delivery", "product_id", "product_type", "product_name", "product_info", "article_number", "amount", "unit", "price", + "price_setup", "vatrate", "billing_period", "create_by","edit_by","create","edit" ]; diff --git a/application/PreorderBilling/PreorderBillingController.php b/application/PreorderBilling/PreorderBillingController.php index b6bfb311a..55e265869 100644 --- a/application/PreorderBilling/PreorderBillingController.php +++ b/application/PreorderBilling/PreorderBillingController.php @@ -230,6 +230,19 @@ class PreorderBillingController extends mfBaseController { } $netoperator_config = $netowner_config["netoperators"][$netoperator->id]; + $fibu_cost_code = TT_PREORDER_BILLING[$netowner_id]["fibu-cost-code"]; + if($fibu_cost_code == "=from-campaign") { + $campaign_cost_code = $preorder->campaign->netowner_fibu_cost_code; + if(!$campaign_cost_code) { + die("campaign cost_code not found for preorder ".$preorder->id." campaign ".$preorder->campaign->id); + } + $fibu_cost_code = $campaign_cost_code; + } + //var_dump($fibu_cost_code);exit; + if(!$fibu_cost_code) { + die("fibu_cost_code not found for preorder ".$preorder->id); + } + $bill_params = [ "netowner" => $netowner, "netowner_config" => $netowner_config, @@ -240,7 +253,8 @@ class PreorderBillingController extends mfBaseController { "bill_date" => $bill_date, "earliest_bill_date" => $earliest_bill_date, "latest_bill_date" => $latest_bill_date, - "latest_quarter_bill_date" => $latest_quarter_bill_date + "latest_quarter_bill_date" => $latest_quarter_bill_date, + "fibu_cost_code" => $fibu_cost_code, ]; if($preorder->status->code >= 241) { @@ -260,6 +274,9 @@ class PreorderBillingController extends mfBaseController { } + /********************************* + * Enduser Setup & Netoperator Setup Billing + */ private function billSetup($preorder, $type, $options) { $netowner = $options['netowner']; $netowner_config = $options['netowner_config']; @@ -271,6 +288,7 @@ class PreorderBillingController extends mfBaseController { $latest_bill_date = $options['latest_bill_date']; $earliest_bill_date = $options['earliest_bill_date']; $latest_quarter_bill_date = $options["latest_quarter_bill_date"]; + $fibu_cost_code = $options['fibu_cost_code']; $this->log->debug(__METHOD__.": bill $type Preorder ".$preorder->id); @@ -338,6 +356,7 @@ class PreorderBillingController extends mfBaseController { "preorder_id" => $preorder->id, "oaid" => $preorder->oaid, "adb_wohneinheit_id" => $preorder->adb_wohneinheit_id, + "extref" => ($preorder->extref) ?: null, "order_date" => $order_date->format("Y-m-d"), "start_date" => $status_change_date->format("Y-m-d"), "end_date" => $status_change_date->format("Y-m-d"), @@ -352,11 +371,19 @@ class PreorderBillingController extends mfBaseController { "price_setup" => round($price->price_setup, 2), "vatrate" => 20, "billing_period" => 0, + "fibu_cost_account" => $fibu_cost_code, ]; + + $fibu_revenue_code = ""; if($type == "enduser_setup") { // Endkunde Setup Gebühr + $fibu_revenue_code = $netowner_config["fibu-revenue-code"]; + if(!$fibu_revenue_code) { + die("fibu_revenue_code not found for preorder ".$preorder->id); + } + if($order_date->format("Ymd") < $earliest_bill_date->format("Ymd")) { return true; } @@ -394,13 +421,21 @@ class PreorderBillingController extends mfBaseController { $billing_data[$key] = $value; } - $address = $preorder->adb_hausnummer->strasse->name." ".$preorder->adb_hausnummer->hausnummer." ".($preorder->adb_hausnummer->stiege ? "/".$preorder->adb_hausnummer->stiege : "").", ".$preorder->adb_hausnummer->plz->plz." ".$preorder->adb_hausnummer->ortschaft->name; + $address = $preorder->adb_hausnummer->strasse->name." ".$preorder->adb_hausnummer->hausnummer.($preorder->adb_hausnummer->stiege ? " /".$preorder->adb_hausnummer->stiege : "").", ".$preorder->adb_hausnummer->plz->plz." ".$preorder->adb_hausnummer->ortschaft->name; $billing_data["preorderbillingcustomer_id"] = $customer->id; $billing_data["fibu_account_number"] = $customer->fibu_account_number; + $billing_data["fibu_revenue_account"] = $fibu_revenue_code; $billing_data["product_name"] = "Herstellungsentgelt Glasfaser-Internetanschluss"; $billing_data["product_info"] = $address; } elseif($type == "operator_setup") { + if(array_key_exists("fibu-revenue-code", $netoperator_config) && $netoperator_config["fibu-revenue-code"]) { + $fibu_revenue_code = $netoperator_config["fibu-revenue-code"]; + } + if(!$fibu_revenue_code) { + die("fibu_revenue code not found for preorder ".$preorder->id); + } + $change_to_active = PreorderHistoryModel::getFirstStatusChangeTo($preorder->id, 500); if($change_to_active) { $status_change_date = new DateTime("@".$change_to_active->changed); @@ -423,6 +458,7 @@ class PreorderBillingController extends mfBaseController { $billing_data["owner_id"] = $netoperator->id; $billing_data["billingaddress_id"] = $netoperator->id; $billing_data["fibu_account_number"] = $netoperator->attributes['rml-fibu-account']->value; + $billing_data["fibu_revenue_account"] = $fibu_revenue_code; $billing_data["company"] = trim($netoperator->company); $billing_data["firstname"] = trim($netoperator->firstname); $billing_data["lastname"] = trim($netoperator->lastname); @@ -451,6 +487,9 @@ class PreorderBillingController extends mfBaseController { return true; } + /********************************* + * Usage Billing + */ private function billOperatorPeriodic($preorder, $options) { $netowner = $options['netowner']; $netowner_config = $options['netowner_config']; @@ -462,12 +501,24 @@ class PreorderBillingController extends mfBaseController { $latest_bill_date = $options['latest_bill_date']; $earliest_bill_date = $options['earliest_bill_date']; $latest_quarter_bill_date = $options["latest_quarter_bill_date"]; + $fibu_cost_code = $options['fibu_cost_code']; + + $cancel_date = false; + $campaign = new PreorderCampaign($preorder->preordercampaign_id); if(!$campaign) { die("Campaign ".$preorder->preordercampaign_id." not found!"); } + $fibu_revenue_code = ""; + if(array_key_exists("fibu-revenue-code", $netoperator_config) && $netoperator_config["fibu-revenue-code"]) { + $fibu_revenue_code = $netoperator_config["fibu-revenue-code"]; + } + if(!$fibu_revenue_code) { + die("fibu_revenue code not found for preorder ".$preorder->id); + } + if(!array_key_exists($campaign->id, $this->marketshare)) { $this->marketshare[$campaign->id] = []; $this->marketshare[$campaign->id]["max"] = $campaign->getUnitCount(); @@ -479,12 +530,6 @@ class PreorderBillingController extends mfBaseController { $this->marketshare[$campaign->id]["netops"][$netoperator->id]["bracket_price"] = []; } - if($preorder->status->code >= 899) { - $this->log->debug(__METHOD__.": Preorder ".$preorder->id." / ".$preorder->oaid." is cancelled"); - // TODO: is cancelled, so determine if refund is necessary - return true; - } - // get price_setup $product = PreorderProduct::getFirst(["type" => "operator_usage"]); if(!$product) { @@ -518,6 +563,22 @@ class PreorderBillingController extends mfBaseController { return true; } + if($preorder->status->code >= 899) { + $this->log->debug(__METHOD__.": Preorder ".$preorder->id." / ".$preorder->oaid." is cancelled"); + // get cancel date + + if(!$status_change) { + $this->log->debug(__METHOD__.": But was never 500, so skipping"); + return true; + } + + $cancel_change = PreorderHistoryModel::getLastStatusChangeToOrHigher($preorder->id, 899); + if(!$cancel_change) { + die("Preorder ".$preorder->oaid." gekündigt (Status ".$preorder->status->code."), aber kein Cancel date gefunden"); + } + $cancel_date = new DateTime("@".$cancel_change->changed); + } + $first_bill_date = clone $status_change_date; @@ -581,6 +642,9 @@ class PreorderBillingController extends mfBaseController { $end_date->modify("+1 months"); $end_date->modify("-1 day"); + if($cancel_date && $cancel_date->format("Ym") == $start_date->format("Ym")) { + $end_date = clone $cancel_date; + } $sday = $start_date->format("d"); $eday = $end_date->format("d"); @@ -622,7 +686,7 @@ class PreorderBillingController extends mfBaseController { $bill_price = $base_price; - if ($base_price && ($sday > 1)) { + if ($base_price && ($sday > 1 || $cancel_date)) { // Aliquoten Preis errechnen $first_of_period = clone $start_date; $first_of_period->modify("first day of this month"); @@ -667,6 +731,8 @@ class PreorderBillingController extends mfBaseController { "owner_id" => $netoperator->id, "billingaddress_id" => $netoperator->id, "fibu_account_number" => $netoperator->attributes['rml-fibu-account']->value, + "fibu_cost_account" => $fibu_cost_code, + "fibu_revenue_account" => $fibu_revenue_code, "company" => trim($netoperator->company), "firstname" => trim($netoperator->firstname), "lastname" => trim($netoperator->lastname), @@ -685,6 +751,9 @@ class PreorderBillingController extends mfBaseController { } $ms_bill_month = $start_date->format("Ym"); + if(!array_key_exists("billed-$ms_bill_month", $this->marketshare[$campaign->id]["netops"][$netoperator->id])) { + $this->marketshare[$campaign->id]["netops"][$netoperator->id]["billed-$ms_bill_month"] = 0; + } $this->marketshare[$campaign->id]["netops"][$netoperator->id]["billed-$ms_bill_month"]++; //$this->marketshare[$campaign->id]["netops"][$netoperator->id]["billed-$ms_bill_month"] } diff --git a/application/PreorderBillingInvoice/PreorderBillingInvoice.php b/application/PreorderBillingInvoice/PreorderBillingInvoice.php index a712feea1..2b60286d3 100644 --- a/application/PreorderBillingInvoice/PreorderBillingInvoice.php +++ b/application/PreorderBillingInvoice/PreorderBillingInvoice.php @@ -10,6 +10,9 @@ class PreorderBillingInvoice extends mfBaseModel { private $pdf; private $csv; + private $creator; + private $editor; + public static function getNextInvoiceNumber($netowner_id) { $last_invoice_num = self::getLastInvoiceNumber($netowner_id); @@ -135,7 +138,87 @@ RML Infrastruktur GmbH"; return (new QRCode)->render($epc); } + public function sendByEmail($to_email = false) { + if(!$this->id) return false; + // get pdf file + $ifile = PreorderBillingInvoiceFile::createFromInvoice($this); + if(!$ifile) { + $this->layout()->setFlash("Fehler beim PDF erstellen"); + $this->redirect("PreorderBillingInvoice"); + } + $pdf = $ifile->file; + + $pdf_filename = false; + try { + $pdf_filename = $pdf->getFullPath(); + } catch(\Exception $e) { + $this->log->error("PDF-File for Invoice ".$this->id." not found"); + } + if(!$pdf_filename || !file_exists($pdf_filename)) { + return false; + } + + // get csv file + $csv_filename = false; + $csv = $this->getProperty("csv"); + if($csv) { + try { + $csv_filename = $csv->getFullPath(); + } catch (\Exception $e) { + $this->log->error("CSV-File for Invoice " . $this->id . " not found"); + } + } + + $tpl = new Layout(); + $tpl->setTemplate("Emailtemplates/preorder-invoice/rml/invoice-email"); + + $pdf_vars = [ + "invoice" => $this + ]; + + foreach($pdf_vars as $name => $val) { + $tpl->set($name, $val); + } + + $body = $tpl->render(); + $values = $tpl->getReturnedValue(); + + $subject = $values['subject']; + $from = $values['from_email']; + $from_name = $values['from_email_name']; + if($to_email) { + $to = $to_email; + } else { + $to = trim($this->email); + } + + if(!$to) { + $this->log->error(__METHOD__.": Invoice ".$this->invoice_number." missing email"); + } + + + + if(!$subject || !$from || !$from_name || !$to) { + $this->log->warn(__METHOD__.": Invoice ".$this->invoice_number." could not be sent. Values missing. (subject: '$subject', from: '$from_name', from_email: '$from', to: '$to')"); + return false; + } else { + $email = new Emailnotification(); + $email->setSubject($subject); + $email->setBody($body); + $email->setFrom($from, $from_name); + $email->setTo($to); + $email->setHeader("X-".MFAPPNAME."-Iid", $this->id); + $email->addAttachment($pdf_filename, null, $pdf->filename, "application/pdf"); + if($csv_filename) { + $email->addAttachment($csv_filename, null, $csv->filename, "text/csv"); + } + $email->send(); + $this->log->info(__METHOD__.": Sending Preorder Invoice ".$this->invoice_number." to $to"); + } + + return true; + } public function getProperty($name) { if($this->$name == null) { @@ -147,7 +230,7 @@ RML Infrastruktur GmbH"; } if($name == "pdf") { - $ifile = PreorderBillingInvoiceFile::getFirst(["invoice_id" => $this->id]); + $ifile = PreorderBillingInvoiceFile::getFirst(["invoice_id" => $this->id, "name" => "%.pdf"]); if(!$ifile) return null; $file = $ifile->file; @@ -481,6 +564,16 @@ RML Infrastruktur GmbH"; } } + if(array_key_exists("date_delivered", $filter)) { + $date_delivered = $filter['date_delivered']; + if($date_delivered === null || $date_delivered === false) { + $where .= " AND (PreorderBillingInvoice.date_delivered IS NULL OR PreorderBillingInvoice.date_delivered=0)"; + } elseif($date_delivered === true) { + $where .= " AND (PreorderBillingInvoice.date_delivered IS NOT NULL AND PreorderBillingInvoice.date_delivered > 0)"; + } elseif(is_numeric($date_delivered)) { + $where .= " AND PreorderBillingInvoice.date_delivered=$date_delivered"; + } + } if(array_key_exists("add-where", $filter)) { $where .= " ".$filter['add-where']; diff --git a/application/PreorderBillingInvoice/PreorderBillingInvoiceController.php b/application/PreorderBillingInvoice/PreorderBillingInvoiceController.php index 8ef72310f..af809bbab 100644 --- a/application/PreorderBillingInvoice/PreorderBillingInvoiceController.php +++ b/application/PreorderBillingInvoice/PreorderBillingInvoiceController.php @@ -12,7 +12,7 @@ class PreorderBillingInvoiceController extends mfBaseController { $this->me = $me; $this->layout()->set("me", $me); - if(!$me->can(["preorderbilling", "preorderbillingReadonly"])) { + if(!$me->can(["preorderbilling", "preorderbillingReadonly"]) && (!defined("MFBASE_BYPASS_LOGIN") || !MFBASE_BYPASS_LOGIN)) { $this->redirect("Dashboard"); } } @@ -54,6 +54,10 @@ class PreorderBillingInvoiceController extends mfBaseController { $this->layout()->set("invoices", $invoices); $this->layout()->set("pagination", $pagination); + $now = new DateTime("now"); + $email_jobs = InvoiceJobModel::search(["task" => "send-preorder-invoice-email", "to_date>=" => $now->format("Y-m-d")]); + $this->layout()->set("email_jobs", $email_jobs); + } private function getPreparedFilter($filter) { @@ -165,6 +169,47 @@ class PreorderBillingInvoiceController extends mfBaseController { exit; } + protected function createJob() { + $r = $this->request; + //var_dump($r->get());exit; + + $task = $r->task; + if($task != "send-preorder-invoice-email") { + $this->layout()->setFlash("Ungültiger Task", "error"); + $this->redirect("PreorderBillingInvoice"); + } + + if(!$r->from_date) { + $this->layout()->setFlash("Startdatum wird benötigt", "error"); + $this->redirect("PreorderBillingInvoice"); + } + + try { + $from_date = DateTime::createFromFormat("d.m.Y", $r->from_date, new DateTimeZone("Europe/Vienna")); + $from_date->setTime(0,0,0); + + $to_date = clone $from_date; + $to_date->modify("+7 days"); + $to_date->setTime(23,59,59); + } catch(Exception $e) { + $this->layout()->setFlash("Von- oder Bisdatum ungültig", "error"); + $this->redirect("PreorderBillingInvoice"); + } + + $job = InvoiceJobModel::create([ + "task" => $task, + "from_date" => $from_date->format("Y-m-d"), + "to_date" => $to_date->format("Y-m-d"), + ]); + if(!$job->save()) { + $this->layout()->setFlash("Fehler beim Speichern", "error"); + $this->redirect("PreorderBillingInvoice"); + } + + $this->layout()->setFlash("Task erfolgreich gespeichert", "success"); + $this->redirect("PreorderBillingInvoice"); + } + protected function createAction() { $netowner_id = $this->me->address_id; $today = new DateTime(); @@ -266,6 +311,7 @@ class PreorderBillingInvoiceController extends mfBaseController { "company" => ($bill->company) ?: $bill->firstname . " " . $bill->lastname, "network_name" => $campaign_name, "oaid" => $bill->oaid, + "extref" => trim($bill->extref), "order_date" => $bill->order_date, "start_date" => $start_date->format("Y-m-d"), "end_date" => $end_date->format("Y-m-d"), @@ -286,11 +332,13 @@ class PreorderBillingInvoiceController extends mfBaseController { $position_data["product_type"] = $bill->product_type; $position_data["article_number"] = $bill->article_number; $position_data["product_name"] = $bill->product_name; - $position_data["product_info"] = $bill->product_info; + $position_data["article_info"] = $bill->product_info; $position_data["total"] = round($price, 2); $position_data["total_gross"] = round($price_gross, 2); $position_data["vatrate"] = $bill->vatrate; $position_data["billing_id"] = $bill->id; + $position_data["fibu_cost_account"] = $bill->fibu_cost_account; + $position_data["fibu_revenue_account"] = $bill->fibu_revenue_account; if (!array_key_exists($bill->product_id, $invoice_positions)) { $invoice_positions[$bill->product_id] = []; @@ -327,6 +375,9 @@ class PreorderBillingInvoiceController extends mfBaseController { $new_pos_data["product_type"] = $position["product_type"]; $new_pos_data["article_number"] = $position["article_number"]; $new_pos_data["article_name"] = $position["product_name"]; + $new_pos_data["article_info"] = $position["article_info"]; + $new_pos_data["fibu_cost_account"] = $position["fibu_cost_account"]; + $new_pos_data["fibu_revenue_account"] = $position["fibu_revenue_account"]; $new_pos_data["preorder_billings"][] = $position["billing_id"]; $positions_count++; @@ -340,6 +391,8 @@ class PreorderBillingInvoiceController extends mfBaseController { $new_position = PreorderBillingInvoiceposition::create($new_pos_data); $new_position->preorder_billings = $new_pos_data["preorder_billings"]; $new_position->count = $new_pos_data["count"]; + $new_position->fibu_cost_account = $new_pos_data["fibu_cost_account"]; + $new_position->fibu_revenue_account = $new_pos_data["fibu_revenue_account"]; $sort_key = round(3 / $product_id)."-".$year_month; $positions_to_sort[$sort_key] = $new_position; @@ -365,14 +418,14 @@ class PreorderBillingInvoiceController extends mfBaseController { //exit; + + $invoice_data = []; $invoice_data["netowner_id"] = $netowner_id; $invoice_data["preorderbillingcustomer_id"] = $bill->preorderbillingcustomer_id; $invoice_data["owner_id"] = $bill->owner_id; $invoice_data["billingaddress_id"] = $bill->billingaddress_id; $invoice_data["fibu_account_number"] = $bill->fibu_account_number; - $invoice_data["fibu_cost_account"] = TT_PREORDER_BILLING[$netowner_id]["fibu-cost-code"]; - $invoice_data["fibu_revenue_account"] = TT_PREORDER_BILLING[$netowner_id]["fibu-revenue-code"]; $invoice_data["fibu_tax_code"] = 1; $invoice_data["tax_text"] = null; $invoice_data["head_text"] = null; @@ -403,7 +456,8 @@ class PreorderBillingInvoiceController extends mfBaseController { $total_net = 0; $total_gross = 0; - + $fibu_cost_account = ""; + $fibu_revenue_account = ""; foreach($sorted_positions as $position) { //var_dump($position); $this->log->debug(__METHOD__.": count: ".$position->count); @@ -412,6 +466,11 @@ class PreorderBillingInvoiceController extends mfBaseController { unset($position->preorder_billings); unset($position->count); + $fibu_cost_account = $position->fibu_cost_account; + $fibu_revenue_account = $position->fibu_revenue_account; + unset($position->fibu_cost_account); + unset($position->fibu_revenue_account); + if($position->product_type != "enduser_setup") { if(array_key_exists($netoperator_id, TT_PREORDER_BILLING[$netowner_id]["netoperators"]) && TT_PREORDER_BILLING[$netowner_id]["netoperators"][$netoperator_id]["billing-period"] == "quarterly") { @@ -469,6 +528,8 @@ class PreorderBillingInvoiceController extends mfBaseController { $invoice->total_gross = $total_gross; $invoice->invoice_number = $new_num; $invoice->invoice_date = $today->format("Y-m-d"); + $invoice->fibu_cost_account = $fibu_cost_account; + $invoice->fibu_revenue_account = $fibu_revenue_account; // voicenumbers //var_dump($invoice_voicenumbers);exit; @@ -494,7 +555,7 @@ class PreorderBillingInvoiceController extends mfBaseController { if(count($invoice_detail_data)) { // create CSV file //var_dump($invoice_detail_data);exit; - $csv = "\u{FEFF}Netzbetreiber;Rechungsnummer;Rechnungsdateum;Netzgebiet;OAID;Bestelldatum;Periode von;Periode bis;Artikelnummer;Produkt;Anzahl;Preis Netto\n"; + $csv = "\u{FEFF}Netzbetreiber;Rechungsnummer;Rechnungsdateum;Netzgebiet;OAID;Extref;Bestelldatum;Periode von;Periode bis;Artikelnummer;Produkt;Anzahl;Preis Netto\n"; foreach ($invoice_detail_data as $detail) { //var_dump($detail); //exit; @@ -504,6 +565,7 @@ class PreorderBillingInvoiceController extends mfBaseController { $csv .= '"'.$invoice->invoice_date.'";'; $csv .= '"'.str_replace('"','""',$detail["network_name"]).'";'; $csv .= '"'.$detail["oaid"].'";'; + $csv .= '"'.str_replace('"','""',$detail["extref"]).'";'; $csv .= '"'.$detail["order_date"].'";'; $csv .= '"'.$detail["start_date"].'";'; $csv .= '"'.$detail["end_date"].'";'; @@ -559,4 +621,94 @@ class PreorderBillingInvoiceController extends mfBaseController { $this->layout()->setFlash("$invoice_count Rechnungen erstellt", "success"); $this->redirect("PreorderBillingInvoice"); } + + public function _sendEmailInvoices($limit = false) { + $sent = 0; + $defer = 0; + $invoices = PreorderBillingInvoice::search(["billing_delivery" => "email", "date_delivered" => false]); + foreach($invoices as $invoice) { + if($limit && $sent >= $limit) { + return ["sent" => $sent, "defer" => $defer]; + } + + /*$pdf = $invoice->pdf; + $pdf = false; + + if(!$pdf || !$pdf->name) { + $ifile = PreorderBillingInvoiceFile::createFromInvoice($invoice); + if(!$ifile) { + $this->layout()->setFlash("Fehler beim PDF erstellen"); + $this->redirect("PreorderBillingInvoice"); + } + $pdf = $ifile->file; + } + + $pdf_file = false; + try { + $pdf_file = $pdf->getFullPath(); + } catch (Exception $e) { + $this->log->error(__METHOD__.": File for Invoice ".$invoice->id." not found"); + continue; + } + + if(!file_exists($pdf_file)) { + $this->log->error(__METHOD__.": Datei ".$pdf->filename." nicht gefunden"); + continue; + } + + if($invoice->total == 0) { + $this->log->info(__METHOD__.": Skipping ".$invoice->invoice_number." because total is zero"); + $invoice->date_delivered = date("U"); + $invoice->save(); + continue; + } + */ + if(!$invoice->sendByEmail()) { + $this->log->warning(__METHOD__.": Error sending ".$invoice->invoice_number." to ".$invoice->email); + $invoice->date_delivered = date("U"); + $invoice->save(); + continue; + } + + $invoice->date_delivered = date("U"); + $invoice->save(); + + $sent++; + + } + + return ["sent" => $sent, "defer" => $defer]; + } + protected function apiAction() { + $do = $this->request->do; + $data = []; + + switch($do) { + case "getActiveJobs": + $return = $this->getActiveJobsApi(); + break; + default: + $return = false; + } + + if(!is_array($return) || !count($return)) { + $data = ["status" => "error"]; + $this->returnJson($data); + } + $data['status'] = "OK"; + $data['result'] = $return; + $this->returnJson($data); + } + + private function getActiveJobsApi() { + $now = new DateTime("now"); + $jobs = []; + foreach(InvoiceJobModel::search(["to_date>=" => $now->format("Y-m-d")]) as $job) { + if(!str_contains($job->task,"preorder")) continue; + $j = $job->data; + $j->id = $job->id; + $jobs[] = $j; + } + return ["jobs" => $jobs]; + } } \ No newline at end of file diff --git a/application/PreorderHistory/PreorderHistoryModel.php b/application/PreorderHistory/PreorderHistoryModel.php index 0552dff68..7413c2ff7 100644 --- a/application/PreorderHistory/PreorderHistoryModel.php +++ b/application/PreorderHistory/PreorderHistoryModel.php @@ -52,7 +52,7 @@ class PreorderHistoryModel { } else { $sql .= " ORDER BY `create` DESC LIMIT 1"; } - //mfLoghandler::singleton()->debug($sql); + mfLoghandler::singleton()->debug($sql); $res = FronkDB::singleton()->query($sql); if(FronkDB::singleton()->num_rows($res)) { $data = FronkDB::singleton()->fetch_object($res); @@ -72,7 +72,7 @@ class PreorderHistoryModel { foreach(PreorderstatusModel::getAll() as $status) { if($status->code < $status_code) continue; - $change = self::getStatusChange("first", $preorder_id, $status_code); + $change = self::getStatusChange("first", $preorder_id, $status->code); if($change) return $change; } @@ -80,10 +80,11 @@ class PreorderHistoryModel { } public static function getLastStatusChangeToOrHigher($preorder_id, $status_code) { - foreach(PreorderstatusModel::getAll() as $status) { + $codes = PreorderstatusModel::getAll(); + foreach($codes as $status) { if($status->code < $status_code) continue; - $change = self::getStatusChange("last", $preorder_id, $status_code); + $change = self::getStatusChange("last", $preorder_id, $status->code); if($change) return $change; } diff --git a/application/Preordercampaign/PreordercampaignController.php b/application/Preordercampaign/PreordercampaignController.php index 17c593132..5950f87bc 100644 --- a/application/Preordercampaign/PreordercampaignController.php +++ b/application/Preordercampaign/PreordercampaignController.php @@ -220,6 +220,7 @@ class PreordercampaignController extends mfBaseController { $data["cifcableurl"] = trim($r->cifcableurl); $data["from_email_name"] = trim($r->from_email_name); $data["from_email"] = trim($r->from_email); + $data["netowner_fibu_cost_code"] = trim($r->netowner_fibu_cost_code); if($r->from) { $data['from'] = self::dateToTimestamp($r->from); diff --git a/application/Preordercampaign/PreordercampaignModel.php b/application/Preordercampaign/PreordercampaignModel.php index b8842d8b6..5603d9c73 100644 --- a/application/Preordercampaign/PreordercampaignModel.php +++ b/application/Preordercampaign/PreordercampaignModel.php @@ -17,6 +17,7 @@ class PreordercampaignModel { public $exist_is_error; public $require_connectiontype; public $allow_unit_update; + public $netowner_fibu_cost_code; public $cifurl; public $cifcableurl; public $from_email_name; diff --git a/db/migrations/20250328140707_preordercampaign_add_fibu_cost_code.php b/db/migrations/20250328140707_preordercampaign_add_fibu_cost_code.php new file mode 100644 index 000000000..af585f29c --- /dev/null +++ b/db/migrations/20250328140707_preordercampaign_add_fibu_cost_code.php @@ -0,0 +1,31 @@ +getEnvironment() == "thetool") { + $table = $this->table("Preordercampaign"); + $table->addColumn("netowner_fibu_cost_code", "string", ["null" => true, "default" => null, "limit" => 64, "after" => "allow_unit_update"]); + $table->update(); + } + + if($this->getEnvironment() == "addressdb") { + + } + } + + public function down(): void + { + if($this->getEnvironment() == "thetool") { + $this->table("Preordercampaign")->removeColumn("netowner_fibu_cost_code")->update(); + } + + if($this->getEnvironment() == "addressdb") { + + } + } +} diff --git a/db/migrations/20250328142831_preorder_billing_add_fibu.php b/db/migrations/20250328142831_preorder_billing_add_fibu.php new file mode 100644 index 000000000..a7889e0bc --- /dev/null +++ b/db/migrations/20250328142831_preorder_billing_add_fibu.php @@ -0,0 +1,35 @@ +getEnvironment() == "thetool") { + $table = $this->table("PreorderBilling"); + $table->addColumn("fibu_cost_account", "string", ["null" => true, "default" => null, "length" => 255, "after" => "fibu_account_number"]); + $table->addColumn("fibu_revenue_account", "string", ["null" => true, "default" => null, "length" => 255, "after" => "fibu_cost_account"]); + $table->update(); + } + + if($this->getEnvironment() == "addressdb") { + + } + } + + public function down(): void + { + if($this->getEnvironment() == "thetool") { + $table = $this->table("PreorderBilling"); + $table->removeColumn("fibu_cost_account"); + $table->removeColumn("fibu_revenue_account"); + $table->update(); + } + + if($this->getEnvironment() == "addressdb") { + + } + } +} diff --git a/db/migrations/20250328181907_preorder_billing_add_extref.php b/db/migrations/20250328181907_preorder_billing_add_extref.php new file mode 100644 index 000000000..b6d102c6b --- /dev/null +++ b/db/migrations/20250328181907_preorder_billing_add_extref.php @@ -0,0 +1,33 @@ +getEnvironment() == "thetool") { + $table = $this->table('PreorderBilling'); + $table->addColumn("extref", "string", ["null" => true, "default" => null, "limit" => 1024, "after" => "adb_wohneinheit_id"]); + $table->update(); + } + + if($this->getEnvironment() == "addressdb") { + + } + } + + public function down(): void + { + if($this->getEnvironment() == "thetool") { + $table = $this->table('PreorderBilling'); + $table->removeColumn("extref"); + $table->update(); + } + + if($this->getEnvironment() == "addressdb") { + + } + } +} diff --git a/scripts/invoice/invoice-job-broker.php b/scripts/invoice/invoice-job-broker.php index b4d98b337..3b794d752 100755 --- a/scripts/invoice/invoice-job-broker.php +++ b/scripts/invoice/invoice-job-broker.php @@ -5,17 +5,15 @@ if (PHP_SAPI !== 'cli') { die("This program can only be run on the command line.\n"); } +require("../../config/config.php"); /* fclose(STDIN); fclose(STDOUT); fclose(STDERR); - $STDIN = fopen('/dev/null', 'r'); -$STDOUT = fopen('/dev/null', 'w'); -$STDERR = fopen('/dev/null', 'w'); +$STDOUT = fopen(BASEDIR.'/var/log/invoice-job-broker.log', 'w'); +$STDERR = fopen(BASEDIR.'/var/log/invoice-job-broker.err.log', 'w'); */ -require("../../config/config.php"); - define('mfUI',"cli"); define('FRONKDB_SQLDEBUG',false); define("MFBASE_BYPASS_LOGIN", true); @@ -88,7 +86,8 @@ $all_pids = []; $valid_tasks = [ "make-invoice-pdf", - "send-invoice-email" + "send-invoice-email", + "send-preorder-invoice-email", ]; while(1) { @@ -171,6 +170,7 @@ while(1) { //echo "looking for runner for job $taskname\n"; cli_set_process_title($proc["processtitle"]); + $include_name = __DIR__ . "/job-runners/" . $taskname . ".php"; if(!file_exists($include_name)) { echo "[$mypid] Runner $include_name not found\n"; diff --git a/scripts/invoice/job-runners/send-preorder-invoice-email.php b/scripts/invoice/job-runners/send-preorder-invoice-email.php new file mode 100644 index 000000000..a8d80888a --- /dev/null +++ b/scripts/invoice/job-runners/send-preorder-invoice-email.php @@ -0,0 +1,74 @@ +started) { + $job->started = $started->format("Y-m-d H:i:s"); + $job->reconnectDB(); + $job->save(); +} + +$job_return = new stdClass(); +$job_return->sent = 0; + +$pdfs_sent = 0; +$defer = false; +$timeout = false; + +if($job->result) { + $job_return = json_decode($job->result); + if(json_last_error() === JSON_ERROR_NONE) { + $pdfs_sent = $job_return->sent; + } else { + $job_return = new stdClass(); + $job_return->sent = 0; + } +} + + +$ic = new PreorderBillingInvoiceController(false); +$ic->reconnectDB(); + +// main loop +do { + $now = new DateTime("now"); + if($now->format("Y-m-d H:i:s") > $job->to_date." 23:59:59") { + $timeout = true; + break; + } + + $email_return = $ic->_sendEmailInvoices(300); + if($email_return["defer"]) { + $defer = true; + } + $sent = $email_return["sent"]; + $pdfs_sent += $sent; + $job_return->sent = $pdfs_sent; + $job->result = json_encode($job_return); + //$job->return = json_encode(["sent" => $sent]); + $job->reconnectDB(); + $job->save(); +} while($sent); + +// prepare job update +if($timeout) { + $job->status = "timeout"; +} elseif($defer) { + echo "email runner: deferring to next run\n"; + $job->status = "defer"; +} else { + $finished = new DateTime("now"); + $job->finished = $finished->format("Y-m-d H:i:s"); + $job->status = "finished"; +} + +$job->reconnectDB(); +$job->save();