Added InvoiceJob
This commit is contained in:
@@ -72,13 +72,11 @@ $pagination_entity_name = "Rechnungen";
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col-6">
|
||||
<button type="submit" class="btn btn-primary">Filter anwenden</button>
|
||||
<a class="btn btn-secondary" href="<?=self::getUrl("Invoice")?>?resetFilter=1">Filter zurücksetzen</a>
|
||||
</div>
|
||||
<div class="col-6 justify-content-end text-right">
|
||||
<button type="submit" name="export_type" value="bmd-credit" class="btn btn-outline-danger"><i class="far fa-fw fa-file-export"></i> Provisionsexport für BMD</button>
|
||||
<button type="submit" name="export_type" value="bmd-invoice" class="btn btn-outline-primary ml-2"><i class="far fa-fw fa-file-export"></i> Rechnungsexport für BMD</button>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary"><i class="fas fa-fw fa-search"></i> Filter anwenden</button>
|
||||
<a class="btn btn-secondary" href="<?=self::getUrl("Invoice")?>?resetFilter=1"><i class="fas fa-fw fa-xmark-large"></i> Filter zurücksetzen</a>
|
||||
<button type="submit" name="export_type" value="bmd-credit" class="btn btn-outline-danger ml-4"><i class="far fa-fw fa-file-export"></i> Provisionsexport für BMD</button>
|
||||
<button type="submit" name="export_type" value="bmd-invoice" class="btn btn-outline-primary ml-1"><i class="far fa-fw fa-file-export"></i> Rechnungsexport für BMD</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -126,13 +124,106 @@ $pagination_entity_name = "Rechnungen";
|
||||
<div class="card">
|
||||
<div class="card-body mb-3">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h4 class="header-title">Rechnungen</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-end">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<h4>PDFs erstellen</h4>
|
||||
<form method="post" action="<?=self::getUrl("Invoice", "createJob")?>">
|
||||
<input type="hidden" name="task" value="make-invoice-pdf" />
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<label class="form-label" for="from_date">Job Startdatum:</label>
|
||||
<input type="text" class="form-control datepicker" name="from_date" value="" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label class="form-label" for="to_date">Job Enddatum:</label>
|
||||
<input type="text" class="form-control datepicker" name="to_date" value="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mt-1"><i class="fas fa-fw fa-circle-plus"></i> Job erstellen</button>
|
||||
|
||||
<?php if(is_array($pdf_jobs) && count($pdf_jobs)): ?>
|
||||
<h5>Aktuelle PDF Jobs</h5>
|
||||
<table class="table table-sm table-striped">
|
||||
<tr>
|
||||
<th>Von</th>
|
||||
<th>Bis</th>
|
||||
<th>Gestartet</th>
|
||||
<th>Beendet</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
<?php foreach($pdf_jobs as $job): ?>
|
||||
<tr id="job-<?=$job->id?>">
|
||||
<td><?=$job->from_date?></td>
|
||||
<td><?=$job->to_date?></td>
|
||||
<td><?=$job->started?></td>
|
||||
<td class="finished"><?=$job->finished?></td>
|
||||
<td>
|
||||
<strong class="status text-monospace <?=(($job->status == "finished") ? "text-success" : ($job->status == "timeout" ? "text-danger" : "text-info"))?>"><?=($job->status) ? $job->status : "neu"?></strong><br />
|
||||
<span class="status-text"><?=$job->getResult("created") ? $job->getResult("created")." Rechungs-PDFs erstellt" : ""?></span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-6 border-left">
|
||||
<h4>Rechungsemails versenden</h4>
|
||||
<form method="post" action="<?=self::getUrl("Invoice", "createJob")?>">
|
||||
<input type="hidden" name="task" value="send-invoice-email" />
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
<label class="form-label" for="from_date">Job Startdatum:</label>
|
||||
<input type="text" class="form-control datepicker" name="from_date" value="" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label class="form-label" for="to_date">Job Enddatum:</label>
|
||||
<input type="text" class="form-control datepicker" name="to_date" value="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mt-1"><i class="fas fa-fw fa-circle-plus"></i> Job erstellen</button>
|
||||
|
||||
<?php if(is_array($email_jobs) && count($email_jobs)): ?>
|
||||
<h5>Aktuelle Email Jobs</h5>
|
||||
<table class="table table-sm table-striped">
|
||||
<tr>
|
||||
<th>Von</th>
|
||||
<th>Bis</th>
|
||||
<th>Gestartet</th>
|
||||
<th>Beendet</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
<?php foreach($email_jobs as $job): ?>
|
||||
<tr id="job-<?=$job->id?>">
|
||||
<td><?=$job->from_date?></td>
|
||||
<td><?=$job->to_date?></td>
|
||||
<td><?=$job->started?></td>
|
||||
<td class="finished"><?=$job->finished?></td>
|
||||
<td>
|
||||
<strong class="status text-monospace <?=(($job->status == "finished") ? "text-success" : ($job->status == "timeout" ? "text-danger" : "text-info"))?>"><?=($job->status) ? $job->status : "neu"?></strong><br />
|
||||
<span class="status-text"><?=$job->getResult("sent") ? $job->getResult("sent")." Emails versendet" : ""?></span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div>
|
||||
<table class="table table-sm table-striped table-bordered">
|
||||
@@ -189,6 +280,12 @@ $pagination_entity_name = "Rechnungen";
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h4 class="header-title">Rechnungen</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
|
||||
@@ -291,6 +388,65 @@ $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(
|
||||
"<?=self::getUrl("InvoiceJob", "api")?>",
|
||||
{
|
||||
do: "getActiveJobs"
|
||||
},
|
||||
function(success) {
|
||||
if(success.status != "OK") return;
|
||||
|
||||
jobs = success.result.jobs;
|
||||
jobs.forEach((job) => {
|
||||
let status = JSON.parse(job.result);
|
||||
if(!status) return;
|
||||
|
||||
if(job.task == "make-invoice-pdf") {
|
||||
$("#job-" + job.id + " .status-text").text(status.created + " Rechungs-PDFs erstellt");
|
||||
} else if(job.task == "send-invoice-email") {
|
||||
$("#job-" + job.id + " .status-text").text(status.sent + " Emails versendet");
|
||||
}
|
||||
|
||||
$("#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, 1000);
|
||||
},
|
||||
"json"
|
||||
);
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
<?php if(is_array($pdf_jobs) && is_array($email_jobs)): ?>
|
||||
status_update = setTimeout(updateStatus, 1000);
|
||||
<?php endif; ?>
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>
|
||||
@@ -108,7 +108,13 @@ XINON GmbH";
|
||||
return false;
|
||||
}
|
||||
|
||||
$pdf_filename = $pdf->getFullPath();
|
||||
$pdf_filename = false;
|
||||
try {
|
||||
$pdf_filename = $pdf->getFullPath();
|
||||
} catch(\Exception $e) {
|
||||
$this->log->error("File for Invoice ".$this->id." not found");
|
||||
}
|
||||
|
||||
if(!$pdf_filename || !file_exists($pdf_filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,17 @@ class InvoiceController extends mfBaseController {
|
||||
$this->layout()->set("invoices", $billings);
|
||||
$this->layout()->set("pagination", $pagination);
|
||||
|
||||
// get Jobs
|
||||
$now = new DateTime("now");
|
||||
$pdf_jobs = InvoiceJobModel::search(["task" => "make-invoice-pdf", "to_date>=" => $now->format("Y-m-d")]);
|
||||
$email_jobs = InvoiceJobModel::search(["task" => "send-invoice-email", "to_date>=" => $now->format("Y-m-d")]);
|
||||
|
||||
$this->layout()->set("pdf_jobs", $pdf_jobs);
|
||||
$this->layout()->set("email_jobs", $email_jobs);
|
||||
|
||||
|
||||
// sum listing
|
||||
|
||||
$sum_price = InvoiceModel::getSumPrice($filter);
|
||||
$sum_price_gross = InvoiceModel::getSumGrossPrice($filter);
|
||||
$sum_price_sepa = InvoiceModel::getSumPrice(array_merge($filter, ["billing_type" => "sepa"]));
|
||||
@@ -202,6 +213,45 @@ class InvoiceController extends mfBaseController {
|
||||
}
|
||||
|
||||
|
||||
protected function createJob() {
|
||||
$r = $this->request;
|
||||
//var_dump($r->get());exit;
|
||||
|
||||
$task = $r->task;
|
||||
if($task != "make-invoice-pdf" && $task != "send-invoice-email") {
|
||||
$this->layout()->setFlash("Ungültiger Task", "error");
|
||||
$this->redirect("Invoice");
|
||||
}
|
||||
|
||||
if(!$r->from_date || !$r->to_date) {
|
||||
$this->layout()->setFlash("Start- und Endddatum werden benötigt", "error");
|
||||
$this->redirect("Invoice");
|
||||
}
|
||||
|
||||
try {
|
||||
$from_date = DateTime::createFromFormat("d.m.Y", $r->from_date, new DateTimeZone("Europe/Vienna"));
|
||||
$from_date->setTime(0,0,0);
|
||||
|
||||
$to_date = DateTime::createFromFormat("d.m.Y", $r->to_date, new DateTimeZone("Europe/Vienna"));
|
||||
$to_date->setTime(23,59,59);
|
||||
} catch(Exception $e) {
|
||||
$this->layout()->setFlash("Von- oder Bisdatum ungültig", "error");
|
||||
$this->redirect("Invoice");
|
||||
}
|
||||
|
||||
$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("Invoice");
|
||||
}
|
||||
|
||||
$this->layout()->setFlash("Task erfolgreich gespeichert", "success");
|
||||
$this->redirect("Invoice");
|
||||
}
|
||||
|
||||
protected function runInvoicingAction() {
|
||||
if(!$this->me->can("Billing")) {
|
||||
@@ -784,9 +834,13 @@ class InvoiceController extends mfBaseController {
|
||||
exit;
|
||||
}
|
||||
|
||||
public function createPDFs() {
|
||||
public function createPDFs($limit = false) {
|
||||
$invoice_path_base = MFUPLOAD_FILE_SAVE_PATH."/".TT_INVOICE_SAVE_SUBFOLDER;
|
||||
$created = 0;
|
||||
foreach(InvoiceModel::getAll() as $invoice) {
|
||||
if($limit && $created >= $limit) {
|
||||
return $created;
|
||||
}
|
||||
if(InvoiceFileModel::getFirst(["invoice_id" => $invoice->id])) {
|
||||
continue;
|
||||
}
|
||||
@@ -803,11 +857,11 @@ class InvoiceController extends mfBaseController {
|
||||
if(!$ifile) {
|
||||
echo "Could not create PDF for ".$invoice->invoice_number."\n";
|
||||
}
|
||||
|
||||
echo ".";
|
||||
$created++;
|
||||
//echo ".";
|
||||
}
|
||||
echo "\n";
|
||||
return true;
|
||||
//echo "\n";
|
||||
return $created;
|
||||
}
|
||||
|
||||
protected function sendInvoicesAction() {
|
||||
@@ -831,28 +885,40 @@ class InvoiceController extends mfBaseController {
|
||||
/*
|
||||
* Gutschriften auch an Xinon
|
||||
*/
|
||||
public function _sendEmailInvoices() {
|
||||
public function _sendEmailInvoices($limit = false) {
|
||||
$sent = 0;
|
||||
$defer = 0;
|
||||
foreach(InvoiceModel::search(["billing_delivery" => "email", "date_delivered" => false]) as $invoice) {
|
||||
if($limit && $sent >= $limit) {
|
||||
return ["sent" => $sent, "defer" => $defer];
|
||||
}
|
||||
$pdf = $invoice->pdf;
|
||||
|
||||
if(!$pdf || !$pdf->name) {
|
||||
echo "PDF für ".$invoice->invoice_number." noch nicht generiert\n";
|
||||
//echo "PDF für ".$invoice->invoice_number." noch nicht generiert\n";
|
||||
$defer++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$pdf_file = $pdf->getFullPath();
|
||||
$pdf_file = false;
|
||||
try {
|
||||
$pdf_file = $pdf->getFullPath();
|
||||
} catch (Exception $e) {
|
||||
$this->log->error("File for Invoice ".$invoice->id." not found");
|
||||
}
|
||||
|
||||
if(!file_exists($pdf_file)) {
|
||||
echo "Datei ".$pdf->filename." nicht gefunden\n";
|
||||
//echo "Datei ".$pdf->filename." nicht gefunden\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if($invoice->total == 0) {
|
||||
echo "Skipping ".$invoice->invoice_number." because total is zero\n";
|
||||
//echo "Skipping ".$invoice->invoice_number." because total is zero\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!$invoice->sendByEmail()) {
|
||||
echo "Error sending ".$invoice->invoice_number." to ".$invoice->email."\n";
|
||||
$this->log->warning("Error sending ".$invoice->invoice_number." to ".$invoice->email);
|
||||
continue;
|
||||
}
|
||||
$invoice->date_delivered = date("U");
|
||||
@@ -863,11 +929,11 @@ class InvoiceController extends mfBaseController {
|
||||
$invoice->sendByEmail("billing@xinon.at");
|
||||
}
|
||||
|
||||
$sent++;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
return ["sent" => $sent, "defer" => $defer];
|
||||
}
|
||||
|
||||
public function printInvoices() {
|
||||
|
||||
12
application/InvoiceJob/InvoiceJob.php
Normal file
12
application/InvoiceJob/InvoiceJob.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
class InvoiceJob extends mfBaseModel {
|
||||
|
||||
public function getResult($variable) {
|
||||
$result = json_decode($this->result);
|
||||
if(json_last_error() === JSON_ERROR_NONE) {
|
||||
return $result->$variable;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
51
application/InvoiceJob/InvoiceJobController.php
Normal file
51
application/InvoiceJob/InvoiceJobController.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
class InvoiceJobController extends mfBaseController {
|
||||
|
||||
protected function init() {
|
||||
$this->needlogin = true;
|
||||
$me = new User();
|
||||
$me->loadMe();
|
||||
$this->me = $me;
|
||||
$this->layout()->set("me", $me);
|
||||
|
||||
if (!$me->is(["Admin"])) {
|
||||
$this->redirect("Dashboard");
|
||||
}
|
||||
}
|
||||
|
||||
protected function apiAction() {
|
||||
if(!$this->me->is(["Admin"])) {
|
||||
$this->redirect("Dashboard");
|
||||
}
|
||||
$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) {
|
||||
$j = $job->data;
|
||||
$j->id = $job->id;
|
||||
$jobs[] = $j;
|
||||
}
|
||||
return ["jobs" => $jobs];
|
||||
}
|
||||
}
|
||||
253
application/InvoiceJob/InvoiceJobModel.php
Normal file
253
application/InvoiceJob/InvoiceJobModel.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
class InvoiceJobModel {
|
||||
public $task;
|
||||
public $from_date;
|
||||
public $to_date;
|
||||
public $started;
|
||||
public $finished;
|
||||
public $result;
|
||||
public $create_by;
|
||||
public $edit_by;
|
||||
public $create;
|
||||
public $edit;
|
||||
|
||||
|
||||
public static function create(Array $data) {
|
||||
$model = new InvoiceJob();
|
||||
|
||||
foreach($data as $field => $value) {
|
||||
if(property_exists(get_called_class(), $field)) {
|
||||
$model ->$field = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$me = new User();
|
||||
$me->loadMe();
|
||||
|
||||
if($model->create_by === null) {
|
||||
$model->create_by = $me->id;
|
||||
}
|
||||
if($model->edit_by === null) {
|
||||
$model->edit_by = $me->id;
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
public static function getAll() {
|
||||
$items = [];
|
||||
|
||||
$db = FronkDB::singleton();
|
||||
|
||||
$res = $db->select("InvoiceJob", "*", "1 = 1 ORDER BY from_date");
|
||||
if($db->num_rows($res)) {
|
||||
while($data = $db->fetch_object($res)) {
|
||||
$items[] = new InvoiceJob($data);
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
|
||||
}
|
||||
|
||||
public static function getFirst($filter) {
|
||||
$db = FronkDB::singleton();
|
||||
|
||||
$where = self::getSqlFilter($filter);
|
||||
$sql = "SELECT * FROM InvoiceJob
|
||||
WHERE $where
|
||||
ORDER BY from_date LIMIT 1";
|
||||
//var_dump($sql);exit;
|
||||
$res = $db->query($sql);
|
||||
if($db->num_rows($res)) {
|
||||
$data = $db->fetch_object($res);
|
||||
$item = new InvoiceJob($data);
|
||||
if($item->id) {
|
||||
return $item;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getLast($filter) {
|
||||
$db = FronkDB::singleton();
|
||||
|
||||
$where = self::getSqlFilter($filter);
|
||||
$sql = "SELECT * FROM InvoiceJob
|
||||
WHERE $where
|
||||
ORDER BY from_date DESC LIMIT 1";
|
||||
|
||||
mfLoghandler::singleton()->debug($sql);
|
||||
|
||||
$res = $db->query($sql);
|
||||
if($db->num_rows($res)) {
|
||||
$data = $db->fetch_object($res);
|
||||
$item = new InvoiceJob($data);
|
||||
if($item->id) {
|
||||
return $item;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function count($filter) {
|
||||
$db = FronkDB::singleton();
|
||||
|
||||
$where = self::getSqlFilter($filter);
|
||||
$sql = "SELECT COUNT(*) as cnt FROM InvoiceJob
|
||||
WHERE $where";
|
||||
|
||||
mfLoghandler::singleton()->debug($sql);
|
||||
|
||||
$res = $db->query($sql);
|
||||
if($db->num_rows($res)) {
|
||||
$data = $db->fetch_object($res);
|
||||
return $data->cnt;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function search($filter, $limit = false, $order = false, $new_db_connection = false) {
|
||||
//var_dump($filter);exit;
|
||||
$items = [];
|
||||
|
||||
if(!$order) {
|
||||
$order = "from_date ASC";
|
||||
}
|
||||
|
||||
$db = FronkDB::singleton();
|
||||
if($new_db_connection) {
|
||||
$db->reconnect();
|
||||
}
|
||||
|
||||
$where = self::getSqlFilter($filter);
|
||||
$sql = "SELECT * FROM InvoiceJob
|
||||
WHERE $where
|
||||
ORDER BY $order";
|
||||
|
||||
if(is_array($limit) && count($limit)) {
|
||||
if(is_numeric($limit['start']) && is_numeric($limit['count'])) {
|
||||
$sql .= " LIMIT ".$limit['start'].", ".$limit['count'];
|
||||
} elseif(is_numeric($limit['count'])) {
|
||||
$sql .= " LIMIT ".$limit['count'];
|
||||
}
|
||||
}
|
||||
|
||||
mfLoghandler::singleton()->debug($sql);
|
||||
|
||||
$res = $db->query($sql);
|
||||
if($db->num_rows($res)) {
|
||||
while($data = $db->fetch_object($res)) {
|
||||
$items[$data->id] = new InvoiceJob($data);
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private static function getSqlFilter($filter) {
|
||||
$where = "1=1 ";
|
||||
//var_dump($filter);exit;
|
||||
$db = FronkDB::singleton();
|
||||
|
||||
//var_dump($filter);exit;
|
||||
|
||||
if(array_key_exists("started", $filter)) {
|
||||
$started = $filter['started'];
|
||||
if($started === null || $started === false) {
|
||||
$where .= " AND InvoiceJob.started IS NULL";
|
||||
} elseif($started === true) {
|
||||
$where .= " AND InvoiceJob.started > 0";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("finished", $filter)) {
|
||||
$finished = $filter['finished'];
|
||||
if($finished === null || $finished === false) {
|
||||
$where .= " AND InvoiceJob.finished IS NULL";
|
||||
} elseif($finished === true) {
|
||||
$where .= " AND InvoiceJob.finished > 0";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("from_date>", $filter)) {
|
||||
$from_date = $db->escape($filter['from_date>']);
|
||||
if($from_date) {
|
||||
$where .= " AND InvoiceJob.from_date > '$from_date'";
|
||||
}
|
||||
}
|
||||
if(array_key_exists("from_date>=", $filter)) {
|
||||
$from_date = $db->escape($filter['from_date>=']);
|
||||
if($from_date) {
|
||||
$where .= " AND InvoiceJob.from_date >= '$from_date'";
|
||||
}
|
||||
}
|
||||
if(array_key_exists("from_date<", $filter)) {
|
||||
$from_date = $db->escape($filter['from_date<']);
|
||||
if($from_date) {
|
||||
$where .= " AND InvoiceJob.from_date < '$from_date'";
|
||||
}
|
||||
}
|
||||
if(array_key_exists("from_date<=", $filter)) {
|
||||
$from_date = $db->escape($filter['from_date<=']);
|
||||
if($from_date) {
|
||||
$where .= " AND InvoiceJob.from_date <= '$from_date'";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("to_date>", $filter)) {
|
||||
$to_date = $db->escape($filter['to_date>']);
|
||||
if($to_date) {
|
||||
$where .= " AND InvoiceJob.to_date > '$to_date'";
|
||||
}
|
||||
}
|
||||
if(array_key_exists("to_date>=", $filter)) {
|
||||
$to_date = $db->escape($filter['to_date>=']);
|
||||
if($to_date) {
|
||||
$where .= " AND InvoiceJob.to_date >= '$to_date'";
|
||||
}
|
||||
}
|
||||
if(array_key_exists("to_date<", $filter)) {
|
||||
$to_date = $db->escape($filter['to_date<']);
|
||||
if($to_date) {
|
||||
$where .= " AND InvoiceJob.to_date < '$to_date'";
|
||||
}
|
||||
}
|
||||
if(array_key_exists("to_date<=", $filter)) {
|
||||
$to_date = $db->escape($filter['to_date<=']);
|
||||
if($to_date) {
|
||||
$where .= " AND InvoiceJob.to_date <= '$to_date'";
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists("task", $filter)) {
|
||||
$task = FronkDB::singleton()->escape($filter["task"]);
|
||||
if ($task) {
|
||||
$where .= " AND task='$task'";
|
||||
}
|
||||
}
|
||||
|
||||
if(array_key_exists("vatgroup_id", $filter)) {
|
||||
$vatgroup_id = $filter['vatgroup_id'];
|
||||
if(is_numeric($vatgroup_id)) {
|
||||
$where .= " AND InvoiceJob.vatgroup_id = '$vatgroup_id'";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(array_key_exists("add-where", $filter)) {
|
||||
$where .= " ".$filter['add-where'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//var_dump($filter, $where);exit;
|
||||
return $where;
|
||||
}
|
||||
|
||||
}
|
||||
43
db/migrations/20240827113849_create_invoice_job.php
Normal file
43
db/migrations/20240827113849_create_invoice_job.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class CreateInvoiceJob extends AbstractMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if($this->getEnvironment() == "thetool") {
|
||||
$table = $this->table("InvoiceJob");
|
||||
$table->addColumn("task", "string", ["null" => false, "limit" => 64]);
|
||||
$table->addColumn("from_date", "date", ["null" => false]);
|
||||
$table->addColumn("to_date", "date", ["null" => false]);
|
||||
$table->addColumn("started", "datetime", ["null" => true, "default" => null]);
|
||||
$table->addColumn("finished", "datetime", ["null" => true, "default" => null]);
|
||||
$table->addColumn("status", "string", ["null" => true, "default" => true, "limit" => 64]);
|
||||
$table->addColumn("result", "json", ["null" => true, "default" => null]);
|
||||
|
||||
$table->addColumn("create_by", "integer", ["null" => false]);
|
||||
$table->addColumn("edit_by", "integer", ["null" => false]);
|
||||
$table->addColumn("create", "integer", ["null" => false]);
|
||||
$table->addColumn("edit", "integer", ["null" => false]);
|
||||
|
||||
$table->save();
|
||||
}
|
||||
|
||||
if($this->getEnvironment() == "addressdb") {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if($this->getEnvironment() == "thetool") {
|
||||
$this->table("InvoiceJob")->drop()->save();
|
||||
}
|
||||
|
||||
if($this->getEnvironment() == "addressdb") {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,12 @@ class FronkDB {
|
||||
$this->query("SET NAMES utf8mb4");
|
||||
}
|
||||
}
|
||||
|
||||
public function reconnect() {
|
||||
$this->instances = [];
|
||||
$this->link = false;
|
||||
$this->connect();
|
||||
}
|
||||
|
||||
public function disconnect() {
|
||||
if($this->link) {
|
||||
|
||||
@@ -39,7 +39,12 @@ class mfBaseController
|
||||
if (!defined('MFUSELOGIN')) define('MFUSELOGIN', false);
|
||||
if (!defined('MFUSEMENU')) define('MFUSEMENU', false);
|
||||
|
||||
if (MFUSELOGIN) {
|
||||
$nologin = false;
|
||||
if(defined("MFBASE_BYPASS_LOGIN")) {
|
||||
$nologin = MFBASE_BYPASS_LOGIN;
|
||||
}
|
||||
|
||||
if (MFUSELOGIN && !$nologin) {
|
||||
// if protected area and not logged in, redirect to mfLogin
|
||||
if ($this->needlogin == true) {
|
||||
if (!mfLoginController::isLoggedIn()) {
|
||||
@@ -138,6 +143,10 @@ class mfBaseController
|
||||
|
||||
}
|
||||
|
||||
public function reconnectDB() {
|
||||
$this->db()->reconnect();
|
||||
}
|
||||
|
||||
public function __call($name, $params)
|
||||
{
|
||||
$methodname = false;
|
||||
|
||||
@@ -32,7 +32,7 @@ class mfBaseModel {
|
||||
* Takes ID or DB row as arguments
|
||||
* @param id or table row $_
|
||||
*/
|
||||
public function __construct($_ = NULL) {
|
||||
public function __construct($_ = NULL, $new_db_connection = false) {
|
||||
$this->log = mfLoghandler::singleton();
|
||||
$this->table = get_class($this);
|
||||
$this->data = new stdClass();
|
||||
@@ -46,6 +46,9 @@ class mfBaseModel {
|
||||
|
||||
if(FRONKDB) {
|
||||
$this->db = FronkDB::singleton();
|
||||
if($new_db_connection) {
|
||||
$this->db->reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
if(method_exists($this, "init")) {
|
||||
@@ -59,6 +62,10 @@ class mfBaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
public function reconnectDB() {
|
||||
$this->db->reconnect();
|
||||
}
|
||||
|
||||
public function load($row) {
|
||||
if(!is_object($this->data)) {
|
||||
$this->data = new stdClass();
|
||||
|
||||
269
scripts/invoice/invoice-job-broker.php
Executable file
269
scripts/invoice/invoice-job-broker.php
Executable file
@@ -0,0 +1,269 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
die("This program can only be run on the command line.\n");
|
||||
}
|
||||
|
||||
/*
|
||||
fclose(STDIN);
|
||||
fclose(STDOUT);
|
||||
fclose(STDERR);
|
||||
|
||||
$STDIN = fopen('/dev/null', 'r');
|
||||
$STDOUT = fopen('/dev/null', 'w');
|
||||
$STDERR = fopen('/dev/null', 'w');
|
||||
*/
|
||||
require("../../config/config.php");
|
||||
|
||||
define('mfUI',"cli");
|
||||
define('FRONKDB_SQLDEBUG',false);
|
||||
define("MFBASE_BYPASS_LOGIN", true);
|
||||
error_reporting(E_ALL & ~(E_NOTICE | E_STRICT | E_DEPRECATED));
|
||||
|
||||
if(defined('MFLOCALE_TIME')) {
|
||||
setlocale(LC_TIME, MFLOCALE_TIME);
|
||||
}
|
||||
if(defined('MFLOCALE_MONETARY')) {
|
||||
setlocale(LC_MONETARY, MFLOCALE_MONETARY);
|
||||
}
|
||||
if(defined('MFLOCALE_NUMERIC')) {
|
||||
setlocale(LC_NUMERIC, MFLOCALE_NUMERIC);
|
||||
}
|
||||
|
||||
require_once(LIBDIR."/mvcfronk/mfRouter/mfRouter.php");
|
||||
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseModel.php");
|
||||
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseController.php");
|
||||
|
||||
$me = new User(1);
|
||||
|
||||
define("INTERNAL_USER_ID", $me->id);
|
||||
define("INTERNAL_USER_USERNAME", $me->username);
|
||||
|
||||
$request = array();
|
||||
|
||||
// Put commandline arguments into $request
|
||||
if(count($argv)) {
|
||||
$args=$argv;
|
||||
array_shift($args); // shift scriptname off of args array
|
||||
foreach($args as $i => $arg) {
|
||||
if(preg_match('/^--(.+)/',$arg,$m)) {
|
||||
if(isset($args[$i+1]) && !preg_match('/^-/',$args[$i+1])) {
|
||||
$request[$m[1]] = $args[$i+1];
|
||||
} else {
|
||||
$request[$m[1]] = true;
|
||||
}
|
||||
} elseif(preg_match('/^-([a-zA-Z])(.+)/', $arg, $m)) {
|
||||
$request[$m[1]] = $m[2];
|
||||
} elseif(preg_match('/^-([^-])/', $arg, $m)) {
|
||||
if(isset($args[$i+1]) && !preg_match('/^-/',$args[$i+1])) {
|
||||
$request[$m[1]] = $args[$i + 1];
|
||||
} else {
|
||||
$request[$m[1]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
require_once(LIBDIR."/mvcfronk/mfRouter/mfRouter.php");
|
||||
$log = mfLoghandler::singleton();
|
||||
|
||||
cli_set_process_title("thetool-invoice-job-broker");
|
||||
pcntl_async_signals(true);
|
||||
pcntl_signal(SIGTERM, 'signalHandler');
|
||||
|
||||
$forkcount = 0;
|
||||
$childpids = [];
|
||||
|
||||
if(pidislocked()) {
|
||||
echo "Invoice Job Broker läuft bereits (pidfile vorhanden)\n";
|
||||
exit;
|
||||
}
|
||||
if(!lockpid()) {
|
||||
$log->error(__FILE__.": Error creating lock file!");
|
||||
die("Error creating lock file!\n");
|
||||
}
|
||||
|
||||
$all_pids = [];
|
||||
|
||||
$valid_tasks = [
|
||||
"make-invoice-pdf",
|
||||
"send-invoice-email"
|
||||
];
|
||||
|
||||
while(1) {
|
||||
$processes = [];
|
||||
|
||||
//sleep(5);
|
||||
//echo "looking for new jobs\n";
|
||||
$now = new DateTime("now");
|
||||
$jobs = InvoiceJobModel::search(["from_date<=" => $now->format("Y-m-d"), "to_date>=" => $now->format("Y-m-d"), "finished" => null], false, false, true);
|
||||
|
||||
/*if(!count($jobs)) {
|
||||
echo "no more jobs. Exiting.\n";
|
||||
break;
|
||||
}*/
|
||||
|
||||
foreach($jobs as $job) {
|
||||
$taskname = $job->task;
|
||||
|
||||
if(!array_key_exists($job->task, $childpids)) {
|
||||
$childpids[$taskname] = [];
|
||||
}
|
||||
|
||||
if($job->started && $job->status != "defer") {
|
||||
//echo "Job ".$job->id." is running already\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$proc = [
|
||||
"job_id" => $job->id,
|
||||
"task" => $taskname,
|
||||
"pid" => false,
|
||||
"processtitle" => "$taskname - running job ".$job->id
|
||||
];
|
||||
|
||||
$processes[] = $proc;
|
||||
}
|
||||
|
||||
if(!count($processes) && !count($all_pids)) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach($processes as $proc) {
|
||||
//echo "process task ".$proc["task"]." pid: ".$proc["pid"]."\n";
|
||||
|
||||
if($proc["pid"]) {
|
||||
// maybe look for new tasks here
|
||||
continue;
|
||||
}
|
||||
|
||||
$taskname = $proc["task"];
|
||||
|
||||
if($childpids[$taskname]) {
|
||||
//echo "cannot start new $taskname job, because another one is running already\n";
|
||||
continue;
|
||||
}
|
||||
//echo "Starting new child\n";
|
||||
$pid = pcntl_fork();
|
||||
|
||||
|
||||
if($pid === -1) {
|
||||
$log->debug("error forking");
|
||||
exit;
|
||||
} elseif($pid > 0) {
|
||||
// in parent
|
||||
$forkcount++;
|
||||
$childpids[$taskname] = $pid;
|
||||
$proc["pid"] = $pid;
|
||||
$all_pids[$pid] = $proc;
|
||||
//sleep(1);
|
||||
} else {
|
||||
// in child
|
||||
$mypid = getmypid();
|
||||
$job_id = $proc["job_id"];
|
||||
$job = new InvoiceJob($job_id, true);
|
||||
$job->status = "inprogress";
|
||||
$job->save();
|
||||
|
||||
try {
|
||||
//echo "in pid $mypid\n";
|
||||
//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";
|
||||
}
|
||||
|
||||
require($include_name);
|
||||
|
||||
//echo "[$mypid] Runner $include_name is finished\n";
|
||||
|
||||
} catch(\Exception $e) {
|
||||
// exit child process on error
|
||||
echo "$mypid caught exception: " . $e->getMessage();
|
||||
echo $e->getTraceAsString()."\n";
|
||||
exit;
|
||||
}
|
||||
exit; // make sure child exits when done
|
||||
}
|
||||
}
|
||||
|
||||
if(count($all_pids)) {
|
||||
$status = false;
|
||||
$return_pid = pcntl_wait($status, WNOHANG);
|
||||
if($return_pid) {
|
||||
echo "child $return_pid returned\n";
|
||||
$pid_proc = $all_pids[$return_pid];
|
||||
$pid_task = $pid_proc["task"];
|
||||
$childpids[$pid_task] = null;
|
||||
unset($all_pids[$return_pid]);
|
||||
}
|
||||
}
|
||||
/*echo "No more PIDs, exiting loop\n";
|
||||
break;*/
|
||||
sleep(5);
|
||||
}
|
||||
|
||||
unlockpid();
|
||||
|
||||
function signalHandler($sig) {
|
||||
//global $continue;
|
||||
global $childpids;
|
||||
global $invoice_job_lock;
|
||||
|
||||
//$continue = false;
|
||||
//echo "in signal handler\n";
|
||||
|
||||
if(count($childpids)) {
|
||||
foreach($childpids as $taskname => $pids) {
|
||||
foreach($pids as $childpid) {
|
||||
posix_kill($childpid, SIGTERM);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
unlockpid();
|
||||
exit;
|
||||
}
|
||||
|
||||
function client_log($pid, $text, $severity = "notice") {
|
||||
global $log;
|
||||
global $is_daemon;
|
||||
|
||||
if($is_daemon) {
|
||||
echo "[".date('Y-m-d H:i:s')."] [$pid] $text\n";
|
||||
}
|
||||
$log->$severity($text);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function pidislocked() {
|
||||
$pid = getmypid();
|
||||
$pidfile = __DIR__."/.invoice-job-broker.lock";
|
||||
if(file_exists($pidfile)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function lockpid() {
|
||||
$pid = getmypid();
|
||||
$pidfile = __DIR__."/.invoice-job-broker.lock";
|
||||
file_put_contents($pidfile, $pid);
|
||||
if(file_exists($pidfile)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function unlockpid() {
|
||||
$pid = getmypid();
|
||||
$pidfile = __DIR__."/.invoice-job-broker.lock";
|
||||
if(file_exists($pidfile)) {
|
||||
unlink($pidfile);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
69
scripts/invoice/job-runners/make-invoice-pdf.php
Normal file
69
scripts/invoice/job-runners/make-invoice-pdf.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* @var $job InvoiceJob
|
||||
* @var $proc Array
|
||||
*/
|
||||
|
||||
/*
|
||||
echo "==========================\n";
|
||||
echo "in make-invoice-pdf-runner\n";
|
||||
echo "==========================\n";
|
||||
*/
|
||||
/*
|
||||
* make-invoice-pdf-runner.php
|
||||
* File is included in Invoice Job Broker
|
||||
*/
|
||||
|
||||
$started = new DateTime("now");
|
||||
$job->started = $started->format("Y-m-d H:i:s");
|
||||
$job->reconnectDB();
|
||||
$job->save();
|
||||
|
||||
$job_return = new stdClass();
|
||||
$job_return->created = 0;
|
||||
|
||||
$pdfs_created = 0;
|
||||
$timeout = false;
|
||||
|
||||
if($job->result) {
|
||||
$job_return = json_decode($job->result);
|
||||
if(json_last_error() === JSON_ERROR_NONE) {
|
||||
$pdfs_created = $job_return->created;
|
||||
} else {
|
||||
$job_return = new stdClass();
|
||||
$job_return->created = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$ic = new InvoiceController(false);
|
||||
$ic->reconnectDB();
|
||||
|
||||
do {
|
||||
$now = new DateTime("now");
|
||||
if($now->format("Y-m-d H:i:s") > $job->to_date." 23:59:59") {
|
||||
$timeout = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$created = $ic->createPDFs(10);
|
||||
|
||||
$pdfs_created += $created;
|
||||
$job_return->created = $pdfs_created;
|
||||
$job->result = json_encode($job_return);
|
||||
//$job->return = json_encode(["created" => $created]);
|
||||
$job->reconnectDB();
|
||||
$job->save();
|
||||
} while($created);
|
||||
|
||||
|
||||
if($timeout) {
|
||||
$job->status = "timeout";
|
||||
} else {
|
||||
$job->status = "finished";
|
||||
}
|
||||
|
||||
$finished = new DateTime("now");
|
||||
$job->finished = $finished->format("Y-m-d H:i:s");
|
||||
$job->reconnectDB();
|
||||
$job->save();
|
||||
74
scripts/invoice/job-runners/send-invoice-email.php
Normal file
74
scripts/invoice/job-runners/send-invoice-email.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* @var $job InvoiceJob
|
||||
*/
|
||||
|
||||
/*
|
||||
echo "==========================\n";
|
||||
echo "in send-invoice-email-runner\n";
|
||||
echo "==========================\n";
|
||||
*/
|
||||
|
||||
$started = new DateTime("now");
|
||||
if(!$job->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 InvoiceController(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();
|
||||
Reference in New Issue
Block a user