WIP Contract/Billing/Invoice 2024-06-28

This commit is contained in:
Frank Schubert
2024-07-01 11:44:19 +02:00
parent 4589e61ab8
commit ae298a0665
30 changed files with 1898 additions and 212 deletions

View File

@@ -33,8 +33,20 @@ $pagination_entity_name = "Billingrecords";
<div class="row">
<div class="col-1">
<label class="form-label" for="filter_owner">Kundennummer</label>
<input type="text" class="form-control" name="filter[customer_number]" id="filter_street" value="<?=(array_key_exists("customer_number", $filter)) ? $filter['customer_number'] : ""?>"/>
<label class="form-label" for="filter_customer_number">Kundennummer</label>
<input type="text" class="form-control" name="filter[customer_number]" id="filter_customer_number" value="<?=(array_key_exists("customer_number", $filter)) ? $filter['customer_number'] : ""?>"/>
</div>
<div class="col-1">
<label class="form-label" for="filter_customer">Kunde</label>
<input type="text" class="form-control" name="filter[customer]" id="filter_customer" value="<?=(array_key_exists("customer", $filter)) ? $filter['customer'] : ""?>"/>
</div>
<div class="col-1">
<label class="form-label" for="filter_address">Adresse</label>
<input type="text" class="form-control" name="filter[address]" id="filter_address" value="<?=(array_key_exists("address", $filter)) ? $filter['address'] : ""?>"/>
</div>
<div class="col-1">
<label class="form-label" for="filter_product">Produkt</label>
<input type="text" class="form-control" name="filter[product]" id="filter_product" value="<?=(array_key_exists("product", $filter)) ? $filter['product'] : ""?>"/>
</div>
<div class="col-2">
<label class="form-label" for="filter_show_credit">Gutschriften</label>
@@ -74,7 +86,7 @@ $pagination_entity_name = "Billingrecords";
</div>
<div class="float-right">
<a class="btn btn-outline-primary mb-2" href="<?=self::getUrl("Billing", "importContracts")?>">
<i class="fas fa-fw fa-file-import"></i> Verrechenbare Contracts importieren
<i class="fas fa-fw fa-file-import"></i> Aktuell verrechenbare Contracts neu importieren
</a>
</div>
</div>
@@ -92,23 +104,24 @@ $pagination_entity_name = "Billingrecords";
<th>Vertragsinhaber</th>
<th>Rechnungsadresse</th>
<th>Kundennummer</th>
<th>Einzug</th>
<th>Zustellung</th>
<th>Bankdaten</th>
<th>Produkt</th>
<th>Anzahl</th>
<th>Preis</th>
<th>Preis Setup</th>
<th>Zahlung</th>
<th>Zustellung</th>
<th>Bankdaten</th>
<th></th>
</tr>
<?php foreach($billings as $billing): ?>
<tr>
<td><a href="<?=self::getUrl("Contract", "view", ["contract_id" => $billing->contract_id])?>" target="_blank"><?=$billing->contract_id?></a></td>
<td><?=$billing->start_date?> - <?=$billing->end_date?></td>
<td><?=$billing->start_date?> -<br /><?=$billing->end_date?></td>
<td>
<?=$billing->contract->owner->getCompanyOrName()?><br />
<?=$billing->contract->owner->street?><br />
<?=$billing->contract->owner->zip?> <?=$billing->contract->owner->city?>
<?=$billing->contract->owner->zip?> <?=$billing->contract->owner->city?><br />
<?=$billing->contract->owner->country->name?>
</td>
<td>
@@ -119,6 +132,17 @@ $pagination_entity_name = "Billingrecords";
<?=$billing->country?>
</td>
<td><?=$billing->customer_number?></td>
<td>
<?=$billing->product_name?><?=($billing->matchcode) ? " (".$billing->matchcode.")" : ""?>
<?php if($billing->product_info): ?>
<div class="pl-2">
<?=$billing->product_info?>
</div>
<?php endif; ?>
</td>
<td><?=($billing->amount / (int)$billing->amount > 1) ? number_format($billing->amount,3,",",".") : (int)$billing->amount?></td>
<td class="<?=($billing->price < 0) ? "text-danger" : ""?>">€ <?=number_format($billing->price,4,",",".")?></td>
<td class="<?=($billing->price_setup < 0) ? "text-danger" : ""?>">€ <?=number_format($billing->price_setup,4,",",".")?></td>
<td><?=($billing->billing_type == "sepa") ? "SEPA" : ""?></td>
<td><?=($billing->billing_delivery == "email") ? "Email" : "Papier"?></td>
<td>
@@ -129,17 +153,7 @@ $pagination_entity_name = "Billingrecords";
BIC: <?=$billing->bank_account_bic?><br />
<?php endif; ?>
</td>
<td>
<?=$billing->product_name?><?=($billing->matchcode) ? " (".$billing->matchcode.")" : ""?>
<?php if($billing->product_info): ?>
<div class="pl-2">
<?=$billing->product_info?>
</div>
<?php endif; ?>
</td>
<td><?=number_format($billing->amount,3,",",".")?></td>
<td>€ <?=number_format($billing->price,4,",",".")?></td>
<td>€ <?=number_format($billing->price_setup,4,",",".")?></td>
<td></td>
</tr>
<?php endforeach; ?>

View File

@@ -148,8 +148,9 @@
<label class="col-lg-2 col-form-label" for="billing_period">Verrechnungsperiode</label>
<div class="col-lg-10">
<select class="form-control" name="billing_period" id="billing_period" placeholder="Verrechnungsperiode">
<option value="1" <?=($product->billing_period == 1) ? "selected='selected'" : ""?>>Monatlich</option>
<option value="12" <?=($product->billing_period == 12) ? "selected='selected'" : ""?>>Jährlich</option>
<option value="0" <?=($contract && $contract->billing_period == 0) ? "selected='selected'" : ""?>>Einmalig</option>
<option value="1" <?=($contract && $contract->billing_period == 1) ? "selected='selected'" : ""?>>Monatlich</option>
<option value="12" <?=($contract && $contract->billing_period == 12) ? "selected='selected'" : ""?>>Jährlich</option>
</select>
</div>
</div>

View File

@@ -131,15 +131,7 @@
<td class="<?=($contract->isCancelled()) ? "canceled" : "" ?> <?=(!$contract->isFinished()) ? "not-finished" : "" ?> <?=($contract->price < 0) ? "text-danger" : ""?>">€ <?=number_format($contract->price,4,",",".")?></td>
<td class="<?=($contract->isCancelled()) ? "canceled" : "" ?> <?=(!$contract->isFinished()) ? "not-finished" : "" ?> <?=($contract->price_setup < 0) ? "text-danger" : ""?>">€ <?=number_format($contract->price_setup,4,",",".")?></td>
<td class="<?=($contract->isCancelled()) ? "canceled" : "" ?> <?=(!$contract->isFinished()) ? "not-finished" : "" ?>">
<?php if($contract->billing_period == 1): ?>
monatlich
<?php elseif($contract->billing_period == 24): ?>
biennal
<?php elseif($contract->billing_period == 36): ?>
triennal
<?php elseif($contract->billing_period): ?>
<?=(12 / $contract->billing_period)?>x Jährlich
<?php endif; ?>
<?=__($contract->billing_period, "billing_period")?>
</td>
<td class="text-monospace <?=(!$contract->isFinished()) ? "not-finished" : "" ?>"><?=($contract->finish_date) ? date('d.m.Y', $contract->finish_date) : ""?></td>
<td class="text-monospace"><?=($contract->cancel_date) ? date('d.m.Y', $contract->cancel_date) : ""?></td>

View File

@@ -136,12 +136,12 @@
<?php endif; ?>
<tr>
<th>Vertragsinhaber:</th>
<td><a href="<?=self::getUrl("Address", "View", ["id" => $contract->owner_id])?>"><?=$contract->owner->getCompanyOrName()?> [<?=$contract->owner->customer_number?>]</a></td>
<td><a href="<?=self::getUrl("Address", "View", ["id" => $contract->owner_id])?>"><?=$contract->owner->getCompanyOrName()?></a> [<?=$contract->owner->customer_number?>]</td>
</tr>
<?php if($contract->billingaddress_id): ?>
<tr>
<th>Rechnungsempfänger:</th>
<td><a href="<?=self::getUrl("Address", "View", ["id" => $contract->billingaddress_id])?>"><?=$contract->billingaddress->getCompanyOrName()?> [<?=$contract->billingaddress->customer_number?>]</a></td>
<td><a href="<?=self::getUrl("Address", "View", ["id" => $contract->billingaddress_id])?>"><?=$contract->billingaddress->getCompanyOrName()?></a> [<?=$contract->billingaddress->customer_number?>]</td>
</tr>
<?php endif; ?>
<tr>
@@ -160,22 +160,42 @@
<th>Externes Produkt:</th>
<td><?=($contract->product_external) ? "Ja" : "Nein"?></td>
</tr><tr>
<th>Setup Preis:</th>
<td class="<?=($contract->price_setup < 0) ? "text-danger" : ""?>">€ <?=$contract->price_setup?></td>
</tr><tr>
<th>Preis Periodisch:</th>
<td class="<?=($contract->price < 0) ? "text-danger" : ""?>">€ <?=$contract->price?></td>
</tr><tr>
<th>Verrechnungsperiode:</th>
<td>
<?=($contract->billing_period == 1) ? "Monatlich" : ""?>
<?=($contract->billing_period == 12) ? "Jährlich" : ""?>
<?=($contract->billing_period == 24) ? "Zweijährlich" : ""?>
<?=($contract->billing_period == 36) ? "Dreijährlich" : ""?>
</td>
<th>Menge:</th>
<td><?=(float)number_format($contract->amount, 3, ",", ".")?></td>
</tr>
<tr>
<th>Preis Periodisch Netto:</th>
<td class="<?=($contract->price < 0) ? "text-danger" : ""?>">€ <?=number_format(($contract->amount != 1) ? $contract->price * $contract->amount : $contract->price, 4, ",", ".")?></td>
</tr><tr>
<th>Preis Periodisch Brutto:</th>
<td class="<?=($contract->price < 0) ? "text-danger" : ""?>">€
<?php if($contract->price && $contract->vatrate): ?>
<?php if($contract->amount != 1): ?>
<?=number_format($contract->price + ($contract->price / 100) * $contract->vatrate, 4, ",", ".")?>
<?php else: ?>
<?=number_format(($contract->price + ($contract->price / 100) * $contract->vatrate) * $contract->amount, 4, ",", ".")?>
<?php endif; ?>
<?php endif; ?>
</td>
</tr><tr>
<th>Verrechnungsperiode:</th>
<td>
<?=__($contract->billing_period, "billing_period")?>
</td>
</tr><tr>
<th>Herstellungskosten:</th>
<td class="<?=($contract->price_setup < 0) ? "text-danger" : ""?>">
<?php if($contract->price_setup > 0): ?>
Netto: € <?=number_format($contract->price_setup, 4, ",", ".")?><?=($contract->amount != 1) ? " (Gesamt: € ".number_format($contract->price_setup * $contract->amount, 4, ",", ".").")" : ""?><br />
Brutto: € <?=($contract->price_setup && $contract->vatrate) ? number_format($contract->price_setup + ($contract->price_setup / 100) * $contract->vatrate, 4, ",", ".") : ""?><?=($contract->price_setup && $contract->vatrate && $contract->amount != 1) ? " (Gesamt: € ".number_format(($contract->price_setup + ($contract->price_setup / 100) * $contract->vatrate) * $contract->amount, 4, ",", ".").")" : ""?></td>
<?php endif; ?>
</tr><tr>
<th>Verrechnungsstart Verzögerung:</th>
<td><?=$contract->billing_delay?> Monate</td>
<td>
<?php if($contract->billing_delay): ?>
<?=$contract->billing_delay?> Monate
<?php endif; ?>
</td>
</tr><tr>
<th></th>
<td></td>
@@ -205,14 +225,14 @@
<td class="text-monospace"><?=date('d.m.Y H:i:s',$contract->edit)?> (<?=$contract->editor->name?>)</td>
</tr><tr class="bg-white">
<td colspan="2" class="text-center">
<a href="<?=self::getUrl("Contractconfig", "edit", ["contract_id" => $contract->id])?>"><button type="button" class="btn btn-sm btn-outline-info">Konfiguration bearbeiten</button></a>
<a href="<?=self::getUrl("Contractaccessletter", "view", ["contract_id" => $contract->id])?>"><button type="button" class="btn btn-sm btn-outline-success">Zugangsdaten anzeigen</button></a>
<a href="<?=self::getUrl("Contractconfig", "edit", ["contract_id" => $contract->id])?>"><button type="button" class="btn btn-sm btn-outline-info"><i class="far fa-list-dropdown fa-fw"></i> Konfiguration bearbeiten</button></a>
<a href="<?=self::getUrl("Contractaccessletter", "view", ["contract_id" => $contract->id])?>"><button type="button" class="btn btn-sm btn-outline-success"><i class="far fa-list-numeric fa-fw"></i> Zugangsdaten anzeigen</button></a>
<?php if($contract->finish_date && $contract->finish_date < date('U')): ?>
<button type="button" class="btn btn-sm btn-outline-secondary">Inhaberwechsel</button>
<a href="<?=self::getUrl("Contract", "productchange", ["contract_id" => $contract->id])?>"><button type="button" class="btn btn-sm btn-outline-purple">Produkt-/Standortwechsel</button></a>
<a href="<?=self::getUrl("Contract", "cancel", ["contract_id" => $contract->id])?>"><button type="button" class="btn btn-sm btn-outline-danger">Kündigen</button></a>
<button type="button" class="btn btn-sm btn-outline-secondary"><i class="far fa-people-arrows fa-fw"></i> Inhaberwechsel</button>
<a href="<?=self::getUrl("Contract", "productchange", ["contract_id" => $contract->id])?>"><button type="button" class="btn btn-sm btn-outline-purple"><i class="far fa-truck-container fa-fw"></i> Produkt-/Standortwechsel</button></a>
<a href="<?=self::getUrl("Contract", "cancel", ["contract_id" => $contract->id])?>"><button type="button" class="btn btn-sm btn-outline-danger"><i class="far fa-axe fa-fw"></i> Kündigen</button></a>
<?php else: ?>
<a href="<?=self::getUrl("Contract", "finishContract", ['contract_id' => $contract->id])?>" onclick="if(!confirm('Jetzt fertigstellen und in Verrechnung geben?')) return false"><button type="button" class="btn btn-sm btn-success">Fertigstellen</button></a>
<a href="<?=self::getUrl("Contract", "finishContract", ['contract_id' => $contract->id])?>" onclick="if(!confirm('Jetzt fertigstellen und in Verrechnung geben?')) return false"><button type="button" class="btn btn-sm btn-success"><i class="far fa-face-confused fa-fw"></i> Fertigstellen</button></a>
<?php endif; ?>
</td>
</tr>
@@ -276,6 +296,11 @@
<em>Vertrag aus Bestellung <a href="<?=self::getUrl("Order", "edit", ["id" => $contract->orderproduct->order_id])?>">#<?=$contract->orderproduct->order_id?></a> erstellt
<?php endif; ?>
</td>
<?php elseif($j->type == "contract_finished"): ?>
<td><i class="fas fa-flag-checkered text-success pl-1"></i></td>
<td style="width: 100%"><em>Vertag fertiggestellt</em>
</td>
<?php elseif($j->type == "credit_created"): ?>
<td><i class="far fa-money-bill-simple-wave text-secondary pl-1"></i></td>
<td style="width: 100%"><em>Gutschrift-Vertrag <a href="<?=self::getUrl("Contract", "View", ["contract_id" => $j->value])?>"><?=$j->value?></a> erstellt</em>
@@ -342,6 +367,8 @@
<th>Kunde</th>
<th>Contract ID</th>
<th>Produkt</th>
<th>Preis</th>
<th>Preis Setup</th>
<th>Bestelldatum</th>
<th>Fertigstellung</th>
<th>Kündigung</th>
@@ -377,12 +404,14 @@
<tr>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?>"><?=__($link->type, "contract")?> <?=($link->type != "link") ? $direction : ""?></td>
<td><a href="<?=self::getUrl("Address", "View", ["id" => $linkcontract->owner_id])?>"><?=$linkcontract->owner->getCompanyOrName()?></a></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?>"><a href="<?=self::getUrl("Contract", "View", ["contract_id" => $linkcontract->id])?>"><?=$linkcontract->id?></a></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?> <?=(!$linkcontract->isFinished()) ? "not-finished" : "" ?>"><a href="<?=self::getUrl("Contract", "View", ["contract_id" => $linkcontract->id])?>"><?=$linkcontract->id?></a></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?>"><a href="<?=self::getUrl("Contract", "View", ["contract_id" => $linkcontract->id])?>"><?=$linkcontract->product_name?> [<?=$linkcontract->matchcode?>]</a></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?>"><?=($linkcontract->order_date) ? date('d.m.Y', $linkcontract->order_date) : ""?></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?>"><?=($linkcontract->finish_date) ? date('d.m.Y', $linkcontract->finish_date) : ""?></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?>"><?=($linkcontract->cancel_date) ? date('d.m.Y', $linkcontract->cancel_date) : ""?></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?> <?=(!$linkcontract->isFinished()) ? "not-finished" : "" ?>"><a href="<?=self::getUrl("Contract", "View", ["contract_id" => $linkcontract->id])?>"><?=$linkcontract->product_name?> [<?=$linkcontract->matchcode?>]</a></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : "" ?> <?=(!$linkcontract->isFinished()) ? "not-finished" : "" ?> <?=($linkcontract->price < 0) ? "text-danger" : ""?>">€ <?=number_format($linkcontract->price,4,",",".")?></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : "" ?> <?=(!$linkcontract->isFinished()) ? "not-finished" : "" ?> <?=($linkcontract->price_setup < 0) ? "text-danger" : ""?>">€ <?=number_format($linkcontract->price_setup,4,",",".")?></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?> <?=(!$linkcontract->isFinished()) ? "not-finished" : "" ?>"><?=($linkcontract->order_date) ? date('d.m.Y', $linkcontract->order_date) : ""?></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?> <?=(!$linkcontract->isFinished()) ? "not-finished" : "" ?>"><?=($linkcontract->finish_date) ? date('d.m.Y', $linkcontract->finish_date) : ""?></td>
<td class="contract <?=($linkcontract->isCancelled()) ? "canceled" : ""?> <?=(!$linkcontract->isFinished()) ? "not-finished" : "" ?>"><?=($linkcontract->cancel_date) ? date('d.m.Y', $linkcontract->cancel_date) : ""?></td>
<td>
<a href="<?=self::getUrl("Contract", "deleteLink", ["link_id" => $link->id])?>" onclick="if(!confirm('Verknüpfung wirklich entfernen?')) return false;" class="text-danger" title="Verknüpfung entfernen"><i class="fas fa-xmark-large"></i></a>
</td>

View File

@@ -0,0 +1 @@
<?php

View File

@@ -0,0 +1,128 @@
<?php
$pagination_baseurl = $this->getUrl($Mod, "Index");
$pagination_baseurl_params = ["filter" => $filter];
$pagination_entity_name = "Billingrecords";
?>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/header.php"); ?>
<!-- start page title -->
<div class="row">
<div class="col-12">
<div class="page-title-box">
<div class="page-title-right">
<ol class="breadcrumb m-0">
<li class="breadcrumb-item"><a href="<?=self::getUrl("Dashboard")?>"><?=MFAPPNAME_SLUG?></a>
</li>
<li class="breadcrumb-item active">Rechnungen</li>
</ol>
</div>
<h4 class="page-title">Rechnungen</h4>
</div>
</div>
</div>
<!-- end page title -->
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-body mb-3">
<h4 class="header-title mb-3">Filter</h4>
<form method="get" action="<?=self::getUrl("Invoice")?>">
<div class="row">
<div class="col-1">
<label class="form-label" for="filter_customer_number">Kundennummer</label>
<input type="text" class="form-control" name="filter[customer_number]" id="filter_customer_number" value="<?=(array_key_exists("customer_number", $filter)) ? $filter['customer_number'] : ""?>"/>
</div>
<div class="col-1">
<label class="form-label" for="filter_customer">Kunde</label>
<input type="text" class="form-control" name="filter[customer]" id="filter_customer" value="<?=(array_key_exists("customer", $filter)) ? $filter['customer'] : ""?>"/>
</div>
<div class="col-1">
<label class="form-label" for="filter_address">Adresse</label>
<input type="text" class="form-control" name="filter[address]" id="filter_address" value="<?=(array_key_exists("address", $filter)) ? $filter['address'] : ""?>"/>
</div>
</div>
<div class="row mt-2">
<div class="col">
<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>
</form>
</div>
</div>
<div class="card">
<div class="card-body mb-3">
<div class="row">
<div class="col-12">
<h4 class="header-title">Rechnungen</h4>
<!--button type="submit" class="btn btn-primary"><i class="fas fa-fw fa-check"></i> Markierte Elemente als Contract übernehmen</button-->
</div>
</div>
<div class="row">
<div class="col-12">
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
<table class="table table-sm table-striped table-hover">
<tr>
<th>Rechungsnummer</th>
<th>Rechnungsdatum</th>
<th>Kundennummer</th>
<th>Rechnungsadresse</th>
<th>Netto</th>
<th>Ust.</th>
<th>Brutto</th>
<th>Zahlung</th>
<th>Zustellung</th>
<th></th>
</tr>
<?php foreach($invoices as $invoice): ?>
<?php $total = $invoice->total + $invoice->total_setup; ?>
<?php $total_gross = $invoice->total_gross + $invoice->total_setup_gross; ?>
<tr>
<td><a href="<?=self::getUrl("Invoice", "downloadInvoice", ["id" => $invoice->id])?>"><i class="fas fa-download fa-fw"></i> <?=$invoice->invoice_number?></a></td>
<td><?=date("d.m.Y", $invoice->invoice_date)?></td>
<td><?=$invoice->customer_number?></td>
<td>
<?=($invoice->company) ? $invoice->company."<br />" : ""?>
<?=($invoice->firstname || $invoice->lastname) ? $invoice->firstname." ".$invoice->lastname."<br />" : ""?>
<?=$invoice->street?><br />
<?=$invoice->zip?> <?=$invoice->city?><br />
<?=$invoice->country?>
</td>
<td class="<?=($total < 0) ? "text-danger" : ""?>">€ <?=number_format($total,2,",",".")?></td>
<td class="<?=($invoice->total_vat < 0) ? "text-danger" : ""?>">€ <?=number_format($invoice->total_vat,2,",",".")?></td>
<td class="<?=($total_gross < 0) ? "text-danger" : ""?>">€ <?=number_format($total_gross,2,",",".")?></td>
<td><?=($invoice->billing_type == "sepa") ? "SEPA" : "Überweisung"?></td>
<td><?=($invoice->billing_delivery == "email") ? "Email" : "Papier"?></td>
<td><a href="<?=self::getUrl("Invoice", "downloadInvoice", ["id" => $invoice->id])?>"><i class="fas fa-download fa-fw"></i></a></td>
</tr>
<?php endforeach; ?>
</table>
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination-summary.php"); ?>
<?php include(realpath(dirname(__FILE__)."/../")."/tpl/pagination.php"); ?>
</div>
</div>
</div>
</div>
</div>
</div>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>

View File

@@ -0,0 +1,158 @@
<?php
/**
* @var string $ressourcePathPrefix
* @var Invoice $invoice
* @var float $vat
*/
$net_total = $invoice->total + $invoice->total_setup;
$gross_total = $invoice->total_gross + $invoice->total_setup_gross;
$is_credit = $net_total < 0;
?>
<html>
<head>
<title>Xinon Billing - Rechnung <?=$invoice->invoice_number?></title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<link href="<?=$ressourcePathPrefix?>css/sknx_print.css" rel="stylesheet" type="text/css" />
</head>
<body>
<table id="adressen">
<tr>
<td class="left">
<div class="adresse">
<div><?=$invoice->company?><br />
<?php if($invoice->firstname && $invoice->lastname): ?><?=$invoice->firstname?> <?=$invoice->lastname?><br /><?php endif; ?>
<?=nl2br($invoice->street)?><br />
<?=$invoice->zip?> <?=$invoice->city?><br />
<?php if($invoice->country != "Österreich"):?><?=$invoice->country?><br /><?php endif; ?>
</div>
<div style="height:40pt;"></div>
<h2><?php if($is_credit):?>Gutschrift<?php else: ?>Rechnung<?php endif; ?> <?=$invoice->invoice_number?></h2>
<div><b><?php if($is_credit):?>Belegdatum<?php else: ?>Rechnungsdatum<?php endif; ?>: <?=date("d.m.Y",$invoice->invoice_date)?></b>
<?php if($invoice->uid): ?><br />Ihre UID: <?=$invoice->uid?><?php endif; ?>
</div>
</td>
<td class="right">
<div class="adresse header_sknx">
<div>
<div><img src="<?=$ressourcePathPrefix?>assets/images/xinon-dark.png" style="width:100%;" /></div>
<div><b>Xinon GmbH</b></div>
<div>Fladnitz im Raabtal 150<br />
A-8322 Studenzen<br />
&nbsp;<br />
Tel +43 3115 40800<br />
office@xinon.at - https://xinon.at/<br />
UID: ATU68711968<br />
FB: FN 416556 h<br />
&nbsp;<br />
Bankverbindung:<br />
Steiermärkische Bank und Sparkassen AG<br />
BIC: STSPAT2GXXX<br />
IBAN: AT84 2081 5000 4007 9121
</div>
</div>
</div>
</td></tr>
</table>
<div style="height:28pt;"></div>
<div id="rechnungspositionen">
<?php if(is_array($invoice->positions) && count($invoice->positions)): ?>
<table class="position">
<tr class="head dontsplit">
<th class="name">Bezeichnung</th>
<th class="preis">Einzelpreis</th>
<th class="menge">Menge</th>
<th class="preis">Gesamtpreis</th>
<th class="preis">Ust.</th>
<th class="preis">Gesamt Brutto</th>
</tr>
<?php $i=0; ?>
<?php foreach($invoice->positions as $p): ?>
<?php
$start_date = new DateTime($p->start_date);
$end_date = new DateTime($p->end_date);
$amount = (float) number_format($p->amount, 3, ",", ".");
$price = number_format($p->price, 2, ",",".");
$price_total = number_format($p->price_total, 2, ",",".");
$price_gross = number_format($p->price_gross, 2, ",",".");
$vatrate = number_format($p->vatrate, 2, ",",".");
?>
<tr class="<?=($i%2 == 0) ? "even" : "odd" ?> one-position">
<td class="name">
<span class="product-name"><?=$p->product_name?></span>
<?php if($start_date->format("d.m.Y") != $end_date->format("d.m.Y")): ?>
&nbsp;(<?=$start_date->format("d.m.Y")?> - <?=$end_date->format("d.m.Y")?>)
<?php endif; ?>
<?=($p->matchcode) ? "<div class='pl-2'>".$p->matchcode."</div>" : ""?>
<?=($p->product_info) ? "<div class='pl-2'>".nl2br($p->product_info)."</div>" : ""?>
</td>
<td class="preis"><?=$price?> €</td>
<td class="preis"><?=$amount?></td>
<td class="preis"><?=$price_total?> €</td>
<td class="preis"><?=$vatrate?>%</td>
<td class="preis"><?=$price_gross?> €</td>
</tr>
<?php $i++; ?>
<?php endforeach; ?>
</table>
<?php endif; ?>
</div>
<div style="height:40pt;"></div>
<table id="total">
<?php if(!$reversecharge): ?>
<tr class="netto">
<th>Gesamtbetrag Netto:</th>
<td><?=number_format($net_total, 2, ",", ".")?> €</td>
</tr>
<?php foreach($vat as $vatrate => $vat_total): ?>
<?php if($vat_total != 0 && $vatrate > 0): ?>
<tr class="ust">
<th>+ Umsatzsteuer <?=$vatrate?>%:</th>
<td><?=number_format($vat_total, 2, ",", ".")?> €</td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
<?php endif; ?>
<tr class="brutto">
<?php if($reversecharge): ?>
<th>Gesamtbetrag:</th>
<?php else: ?>
<th>Gesamtbetrag Brutto:</th>
<?php endif; ?>
<td><?=number_format($gross_total, 2, ",", ".")?> €</td>
</tr>
</table>
<?php if($reversecharge): ?>
<p style="font-weight:bold;">Hinweis: Die Umsatzsteuerschuld geht auf den Leistungsempfänger über (Reverse Charge)</p>
<?php endif; ?>
<?php if($is_credit): ?>
<p>Gutschrift! Bitte nicht überweisen.</p>
<?php else: ?>
Ohne Abzug zu bezahlen bis <?=(new DateTime("@".$invoice->invoice_date))->modify("+14 days")->format("d.m.Y")?>.<br />
Bitte geben Sie als Verwendungszweck unbedingt die Rechnungsnummer an, nur so können wir Ihre Zahlung eindeutig zuordnen
<?php endif; ?>
</body>
</html>

View File

@@ -218,6 +218,12 @@
<label for="can_order" class="form-check-label">Bestellung</label>
</div>
</div>
<div class="col-4">
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" name="can[Billing]" id="can_billing" value="1" <?=$user && $user->can("Billing") ? "checked='checked'" : ""?> />
<label for="can_billing" class="form-check-label">Verrechnung</label>
</div>
</div>
</div>
<h4 class="card-title mb-3 mt-3">Zusatzberechtigungen</h4>

View File

@@ -67,7 +67,7 @@
<li ><a href="<?=self::getUrl("Devicemanufactor")?>"><i class="fad fa-fw fa-router text-info"></i> Geräte Hersteller</a></li>
<li><a href="<?=self::getUrl("Devicetype")?>"><i class="fad fa-fw fa-router text-info"></i> Geräte Typen</a></li>
<li class="has-sub-submenu"><a href="<?=self::getUrl("Device")?>"><i class="fad fa-fw fa-router text-info "></i> Devices</a></li>
<li class="has-sub-submenu"><a href="<?=self::getUrl("User")?>"><i class="fad fa-fw fa-users text-info"></i> Benutzer</a></li>
<li class="has-sub-submenu"><a href="<?=self::getUrl("User")?>"><i class="fad fa-fw fa-users text-info"></i> Benutzer</a></li>
<li class="has-sub-submenu font-weight-bold mt-1 mobile-hide"><a>Grundstammdaten</a></li>
<?php endif; ?>
<?php if($me->is(["Admin"]) || ($me->is("netowner") && $me->hasGwrNetworks())): ?>
@@ -86,6 +86,18 @@
</li>
<?php endif; ?>
<?php if($me->is(["Admin"])): ?>
<li class="has-submenu mobile-hide">
<a href="#">
<i class="fas fa-fw fa-money-from-bracket"></i>Verrechnung <div class="arrow-down"></div>
</a>
<ul class="submenu">
<li><a href="<?=self::getUrl("Contract")?>"><i class="far fa-fw fa-id-card text-info"></i> Contracts</a></li>
<li class="has-sub-submenu"><a href="<?=self::getUrl("Contract", "AdvancedSearch")?>"><i class="far fa-fw fa-question-circle text-info"></i> Erweiterte Suche</a></li>
<?php if($me->is(["Admin"]) && $me->can("Billing")): ?><li><a href="<?=self::getUrl("Billing")?>"><i class="far fa-fw fa-cash-register text-info"></i> Verrechnung</a></li><?php endif; ?>
</ul>
</li>
<?php endif; ?>
<?php if($me->is(["Admin","netowner","lineplanner","pipeplanner","pipeworker","lineworker"])): ?>
<li class="has-submenu mobile-hide">

View File

@@ -199,11 +199,11 @@ class Admin_IvtContractImport {
switch($ip->interval) {
case 0:
$contract_data['billing_period'] = 1;
$finish_date->modify("+1 month");
//$finish_date->modify("+1 month");
break;
case 1:
$contract_data['billing_period'] = 12;
$finish_date->modify("+1 year");
//$finish_date->modify("+1 year");
break;
}
@@ -276,7 +276,7 @@ class Admin_IvtContractImport {
$contract = ContractModel::create($contract_data);
if(trim($ivt_contract->comment)) {
$contract->matchcode = trim($ivt_contract->comment);
} else {
} elseif(!in_array($ivt_contract->id, $this->no_matchcode)) {
$contract->matchcode = $contract->generateMatchcode();
}
/*if($ivt_contract->id == 3035) {
@@ -336,8 +336,6 @@ class Admin_IvtContractImport {
foreach($new_contracts as $ivt_customer_id => $contracts) {
$this->addVoipData($ivt_customer_id, $contracts);
if(in_array($ivt_contract->id, $this->no_matchcode)) break;
if(count($contracts) < 2) continue;
$prev_contracts = [];
$primary_matchcode = false;
@@ -347,10 +345,12 @@ class Admin_IvtContractImport {
foreach($contracts as $contract) {
if($contract->matchcode && !$primary_matchcode && !preg_match($primary_matchcode_no_productgroups_pattern, $contract->product->productgroup->name)) {
if(in_array($ivt_contract->id, $this->no_matchcode)) continue;
$primary_matchcode = $contract->matchcode;
$primary_matchcode_source_product_group = $contract->product->productgroup->name;
}
if($primary_matchcode && !$contract->matchcode && !preg_match($primary_matchcode_no_productgroups_pattern, $primary_matchcode_source_product_group)) {
if(in_array($ivt_contract->id, $this->no_matchcode)) continue;
$contract->matchcode = $primary_matchcode;
$contract->save();
}

View File

@@ -66,6 +66,22 @@ class BillingController extends mfBaseController {
$new_filter["price>="] = 0;
}
if(array_key_exists("customer", $filter)) {
if(array_key_exists("customer", $filter) && $filter["customer"]) {
$kunde = $this->db()->escape($filter['customer']);
if(!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = "";
$new_filter['add-where'] .= " AND (company like '%$kunde%' OR firstname like '%$kunde%' OR lastname like '%$kunde%' OR concat(firstname, ' ', lastname) like '%$kunde%' OR concat(lastname, ' ', firstname) like '%$kunde%')";
}
}
if(array_key_exists("address", $filter)) {
if(array_key_exists("address", $filter) && $filter["address"]) {
$search = $this->db()->escape($filter['address']);
if(!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = "";
$new_filter['add-where'] .= " AND (street like '%$search%' OR zip like '%$search%' OR city like '%$search%' OR country like '%$search%')";
}
}
if (is_array($filter) && count($filter)) {
foreach ($filter as $name => $value) {
$new_filter[$name] = $value;
@@ -86,19 +102,33 @@ class BillingController extends mfBaseController {
$i = 0;
//$yearly_not_before = new DateTime("2023-06-01");
$now_year = date("Y");
$now_month = date("m");
$now_day = date("d");
$now_year = 2024;
$now_month = 7;
// XXX only for 1st Billing after IVT Import
//$yearly_not_before = new DateTime("$now_year-$now_month-01");
$yearly_not_before = new DateTime("$now_year-06-01");
$del = 0;
// first delete all non-invoiced billing records
/*foreach(BillingModel::search(["invoice_id" => null]) as $bill) {
$bill->delete();
$del++;
}*/
$this->log->notice(__METHOD__.": $del Billing records deleted");
//$stop = false;
foreach(ContractModel::search(["finish_date<" => mktime(0,0,0,$now_month, $now_day, $now_year), "cancel_date" => null]) as $contract) {
//while(!$stop) {
//$stop = true;
//$contract = new Contract(1475);
//var_dump($contract);exit;
//$contract = new Contract(1);
$bill_month = $now_month;
$bill_year = $now_year;
//$bill_day = $now_day;
@@ -108,22 +138,27 @@ class BillingController extends mfBaseController {
$monthly_bill_period_to->modify("last day of this month");
$contract_finish_date = new DateTime("@".$contract->finish_date);
$finish_year = date("Y", $contract->finish_date);
$finish_month = date("m", $contract->finish_date);
$finish_day = date("d", $contract->finish_date);
$contract_finish_date->setTimezone(new DateTimeZone("Europe/Vienna"));
$contract_finish_date->setTime(0,0,0);
if($contract->billing_delay) {
// add billing delay to finish_date so the first x months won't be billed
$this->log->debug(__METHOD__.": Adding ".$contract->billing_delay." billing_delay months to finish_date");
$contract_finish_date->modify("+".$contract->billing_delay." months");
}
//echo "$bill_month $bill_year\n";exit;
$finish_year = $contract_finish_date->format("Y");
$finish_month = $contract_finish_date->format("m");
$finish_day = $contract_finish_date->format("d");
//echo $monthly_bill_period_to->format("Y-m-d H:i:s");exit;
if($contract_finish_date > $monthly_bill_period_to) {
$this->log->debug(__METHOD__.": Ignoring Contract ".$contract->id." because finish_date is in $finish_month $finish_year");
continue;
}
if($contract->billing_period < 1) {
/*if($contract->billing_period < 1) {
$this->log->debug(__METHOD__.": Ignoring Contract ".$contract->id." because billing_period == 0");
continue;
}
}*/
if($contract->price == 0 && $contract->price_setup == 0) {
$this->log->debug(__METHOD__.": Ignoring Contract ".$contract->id." because price and price_setup == 0");
continue;
@@ -138,177 +173,156 @@ class BillingController extends mfBaseController {
}
}
$start_date = new DateTime("@".$contract->finish_date);
$start_date->setTimezone(new DateTimeZone("Europe/Vienna"));
//$start_date = clone $contract_finish_date;
// ignore yearly contracts which are not billable this month
if($contract->billing_period == 12) {
if($start_date->format("m") != $bill_month) {
/*if($contract->billing_period == 12) {
if($contract_finish_date->format("m") != $bill_month) {
continue;
}
}
}*/
$create_bills = [];
// Concurrent Billing
// find not yet billed periods
$create_dates = [];
$create_date = clone $start_date;
//$create_date->modify("+".$contract->billing_period." month");
$create_date->modify("first day of this month");
$last_create_date = false;
//echo "first create_date: ".$create_date->format("Y-m-d H:i:s")."<br />\n";
//echo "while ".$create_date->getTimestamp()." (".$create_date->format("Y-m-d H:i:s").") >= ".$contract->finish_date." (".date("Y-m-d H:i:s", $contract->finish_date).")<br>\n";
while($create_date->getTimestamp() >= $contract->finish_date) {
//echo "in need date while (".$create_date->format("Y-m-d H:i:s").")<br>";
if($last_create_date) {
// must for safety / shouldn't happen
die("need-date ran out of dates");
}
//echo "&nbsp;&nbsp;&nbsp;&nbsp;";
//echo $create_date->format("Y")." == ".$finish_year." && ".$create_date->format("m")." == ".$finish_month."<br>";
if($create_date->format("Y") == $finish_year && $create_date->format("m") == $finish_month) {
$create_date->setDate($finish_year, $finish_month, $finish_day);
//echo "set last_create_date true<br>";
$last_create_date = true;
}
$existing_bill = BillingModel::getFirst(["contract_id" => $contract->id, "start_date" => $create_date->format("Y-m-d")]);
//var_dump($need_bill);exit;
if(!$existing_bill) {
//echo "adding date to create_dates[]<br>";
$new_create_date = clone $create_date;
$create_dates[] = $new_create_date;
$create_date->modify("-".$contract->billing_period." months");
if(!$contract->billing_period) {
// setup only
if(BillingModel::getFirst(["contract_id" => $contract->id])) {
continue;
}
break;
}
//var_dump($create_dates);
// find missing billings
foreach($create_dates as $start_date) {
$price_setup = 0;
if($start_date->format("Y") == $finish_year && $start_date->format("m") == $finish_month) {
$price_setup = $contract->price_setup;
}
$create_bills[] = [
"start_date" => $start_date,
"price_setup" => $price_setup // set Setup price to 0, because it was billed already
];
}
/*$last_billings = BillingModel::count(["contract_id" => $contract->id]);
if(!$last_billings) {
// First billing
$start_date->setTime(0,0,0);
//echo "start_date: ".$start_date->format("Y-m-d H:i:s")."<br />";
$create_bills[] = [
"start_date" => $start_date,
"start_date" => $contract_finish_date,
"end_date" => $contract_finish_date,
"price_setup" => $contract->price_setup
];
} else {
// Concurrent Billing
// find not yet billed periods
// contracts with billing period
$create_dates = [];
$create_date = clone $start_date;
$create_date->modify("+1 month");
$create_date->modify("first day of this month");
$last_create_date = false;
//echo "first create_date: ".$create_date->format("Y-m-d H:i:s")."<br />\n";
//echo "while ".$create_date->getTimestamp()." (".$create_date->format("Y-m-d H:i:s").") >= ".$contract->finish_date." (".date("Y-m-d H:i:s", $contract->finish_date).")<br>\n";
//echo "initial bill_date: ".$bill_date->format("Y-m-d")."<br>";
while($create_date->getTimestamp() >= $contract->finish_date) {
//echo "in need date while (".$create_date->format("Y-m-d H:i:s").")<br>";
// if more than 1 month period, adjust initial billing_date to contracts finish_date in current period
// otherwise i.e. yearly contracts older than one year would never be billed ever, or in the best case
// they would be billed a few months too late
if($contract->billing_period > 1 && $bill_date > $contract_finish_date) {
$new_bill_date = clone $contract_finish_date;
$new_bill_date->modify("+".$contract->billing_period." months");
while($new_bill_date->format("Ymd") < $bill_date->format("Ymd")) {
$new_bill_date->modify("+".$contract->billing_period." months");
}
if($new_bill_date->format("Ymd") > $bill_date->format("Ymd")) {
$new_bill_date->modify("-".$contract->billing_period." months");
}
$bill_date = $new_bill_date;
//echo "new bill_date: ".$bill_date->format("Y-m-d");
}
$create_date = clone $bill_date;
$create_date->modify("first day of this month");
$create_date->setTime(0,0,0);
$last_create_date = false;
$earliest_bill_date = clone $contract_finish_date;
$earliest_bill_date->modify("first day of this month");
$earliest_bill_date->setTime(0,0,0);
while($create_date->format("Y-m-d") >= $earliest_bill_date->format("Y-m-d")) {
if($last_create_date) {
// must for safety / shouldn't happen
// just for safety / shouldn't happen
die("need-date ran out of dates");
}
//echo "&nbsp;&nbsp;&nbsp;&nbsp;";
//echo $create_date->format("Y")." == ".$finish_year." && ".$create_date->format("m")." == ".$finish_month."<br>";
if($create_date->format("Y") == $finish_year && $create_date->format("m") == $finish_month) {
// this is the finish month, so set day back to day of finish_date
$create_date->setDate($finish_year, $finish_month, $finish_day);
//echo "set last_create_date true<br>";
$last_create_date = true;
}
$existing_bill = BillingModel::getFirst(["contract_id" => $contract->id, "start_date" => $create_date->format("Y-m-d")]);
//var_dump($need_bill);exit;
if(!$existing_bill) {
//echo "adding date to create_dates[]<br>";
$new_create_date = clone $create_date;
$create_dates[] = $new_create_date;
$create_date->modify("-".$contract->billing_period." months");
$create_date->modify("-" . $contract->billing_period . " months");
continue;
}
break;
}
//var_dump($create_dates);
// find missing billings
foreach($create_dates as $start_date) {
// ignore if last billing row is from this month
/*if($start_date->getTimestamp() < $earliest_next_billing_date->getTimestamp()) {
$this->log->debug(__METHOD__.": last billing row is current billing row. Skip creating new billing row");
continue;
}*//*
$price_setup = 0;
if($start_date->format("Y") == $finish_year && $start_date->format("m") == $finish_month) {
$price_setup = $contract->price_setup;
}
$create_bills[] = [
"start_date" => $start_date,
"price_setup" => 0 // set Setup price to 0, because it was billed already
"price_setup" => $price_setup // set Setup price to 0, because it was billed already
];
}
}*/
}
$create_bills = array_reverse($create_bills);
//var_dump($create_bills);exit;
foreach($create_bills as $bill_data) {
$start_date = $bill_data["start_date"];
$end_date = (array_key_exists("end_date", $bill_data)) ? $bill_data["end_date"] : null;
$price_setup = $bill_data["price_setup"];
if($contract->billing_period > 1 && $start_date < $yearly_not_before) {
// XXX only for 1st Billing after IVT Import
$this->log->debug(__METHOD__.": Ignoring Contract ".$contract->id." with start_date ".$start_date->format("Y-m-d")." because is yearly and before ".$yearly_not_before->format("Y-m-d"));
continue;
}
// if contract has cancel date this month
// use cancel date as end_date
if ($cancel_date) {
$end_date = clone $cancel_date;
} else {
} elseif(!$end_date) {
// else calculate last of month
$end_date = clone $start_date;
$end_date->modify("first day of this month");
$end_date->modify("+" . $contract->billing_period . " months");
//$end_date->modify("first day of this month");
$end_date->modify("-1 day");
}
$sday = $start_date->format("d");
$eday = $end_date->format("d");
if ($sday > 1 || $cancel_date) {
if ($contract->price && ($sday > 1 || $cancel_date)) {
// aliquoter preis
$days = ($eday - $sday) + 1;
//echo "days: $days<br />";
$pc = $days / $eday * 100;
//echo "pc: $pc<br />";
$first_of_period = clone $start_date;
$first_of_period->modify("first day of this month");
$total_days = $end_date->diff($first_of_period)->format("%a") + 1;
$period_days = ($end_date->diff($start_date)->format("%a")) + 1;
$pc = $period_days / $total_days * 100;
$price = round($contract->price / 100 * $pc, 4);
/*if($contract->id == 8766) {
echo "start date: ".$start_date->format("Y-m-d H:i:s") . "<br>";
echo "end date: ".$end_date->format("Y-m-d H:i:s") . "<br><br>";
echo "first_of_period: " . $first_of_period->format("Y-m-d H:i:s") . "<br>";
echo "total days: $total_days<br>";
echo "period days: $period_days<br>";
echo "price: $price<br><br>";
echo "classic days: $days<br>";
exit;
}*/
} else {
$price = $contract->price;
}
/*
echo "contact ID: ".$contract->id."<br />";
echo "contract price: ". $contract->price."<br />";
echo "price: ". $price."<br />";
echo "start_date: ".$start_date->format("Y-m-d H:i:s")."<br />";
echo "sday: $sday<br />";
exit;
*/
$owner = $contract->owner;
$billingaddress = $contract->billingaddress;
@@ -334,6 +348,7 @@ class BillingController extends mfBaseController {
$data["contract_id"] = $contract->id;
$data["start_date"] = $start_date->format("Y-m-d");
$data["end_date"] = $end_date->format("Y-m-d");
$data["owner_id"] = $contract->owner_id;
$data["billingaddress_id"] = ($contract->billingaddress_id) ? $contract->billingaddress_id : $contract->owner_id;
$data["customer_number"] = $contract->owner->customer_number;
$data["company"] = $billingaddress->company;
@@ -351,13 +366,14 @@ class BillingController extends mfBaseController {
$data["bank_account_owner"] = $billingaddress->bank_account_owner;
$data["bank_account_iban"] = $billingaddress->bank_account_iban;
$data["bank_account_bic"] = $billingaddress->bank_account_bic;
$data["matchcode"] = $contract->mathcode;
$data["matchcode"] = $contract->matchcode;
$data["product_id"] = $contract->product_id;
$data["product_name"] = $contract->product_name;
$data["product_info"] = $contract->product_info;
$data["amount"] = $contract->amount;
$data["price"] = $price;
$data["price_setup"] = $price_setup;
$data["vatrate"] = $contract->vatrate;
$data["billing_period"] = $contract->billing_period;
$billing = BillingModel::create($data);

View File

@@ -2,10 +2,10 @@
class BillingModel {
public $invoice_id;
public $invoice_date;
public $contract_id;
public $start_date;
public $end_date;
public $owner_id;
public $billingaddress_id;
public $customer_number;
public $company;
@@ -30,8 +30,8 @@ class BillingModel {
public $amount;
public $price;
public $price_setup;
public $billing_period;
public $vatrate;
public $create_by;
public $edit_by;
public $create;
@@ -117,6 +117,31 @@ class BillingModel {
return null;
}
public static function getInvoiceBaseData($filter) {
$db = FronkDB::singleton();
$items = [];
$where = self::getSqlFilter($filter);
$sql = "SELECT owner_id, billingaddress_id, billing_type, billing_delivery FROM Billing
WHERE $where
GROUP BY owner_id, billingaddress_id, billing_type, billing_delivery
ORDER BY owner_id, billingaddress_id, billing_type, billing_delivery";
//var_dump($sql);exit;
$res = $db->query($sql);
if($db->num_rows($res)) {
while($data = $db->fetch_object($res)) {
$items[] = [
"owner_id" => $data->owner_id,
"billingaddress_id" => $data->billingaddress_id,
"billing_type" => $data->billing_type,
"billing_delivery" => $data->billing_delivery
];
}
}
return $items;
}
public static function count($filter) {
$db = FronkDB::singleton();
@@ -196,16 +221,11 @@ class BillingModel {
$invoice_id = $filter['invoice_id'];
if(is_numeric($invoice_id)) {
$where .= " AND Billing.invoice_id=$invoice_id";
} elseif($invoice_id === null || $invoice_id === false) {
$where .= " AND (Billing.invoice_id IS NULL OR Billing.invoice_id=0)";
}
}
if(array_key_exists("invoice_date", $filter)) {
$invoice_date = $filter['invoice_date'];
if($invoice_date) {
$where .= " AND Billing.invoice_date='$invoice_date'";
}
}
if(array_key_exists("contract_id", $filter)) {
$contract_id = $filter['contract_id'];
if(is_numeric($contract_id)) {
@@ -237,7 +257,7 @@ class BillingModel {
if(array_key_exists("start_date>=", $filter)) {
$start_date = FronkDB::singleton()->escape($filter['start_date>=']);
if($start_date) {
$where .= " AND Billing.start_date >='$start_date'";
$where .= " AND Billing.start_date >= '$start_date'";
}
}
@@ -248,6 +268,13 @@ class BillingModel {
}
}
if(array_key_exists("owner_id", $filter)) {
$owner_id = $filter['owner_id'];
if(is_numeric($owner_id)) {
$where .= " AND Billing.owner_id=$owner_id";
}
}
if(array_key_exists("billingaddress_id", $filter)) {
$billingaddress_id = $filter['billingaddress_id'];
if(is_numeric($billingaddress_id)) {
@@ -384,10 +411,24 @@ class BillingModel {
}
if(array_key_exists("billing_period", $filter)) {
$billing_period = $filter['billing_period'];
if(is_numeric($billing_period)) {
$where .= " AND Billing.billing_period = $billing_period";
if(array_key_exists("billing_type", $filter)) {
$billing_type = $filter['billing_type'];
if(is_numeric($billing_type)) {
$where .= " AND Billing.billing_type = $billing_type";
}
}
if(array_key_exists("billing_type", $filter)) {
$billing_type = $db->escape($filter['billing_type']);
if($billing_type) {
$where .= " AND Billing.billing_type = '$billing_type'";
}
}
if(array_key_exists("billing_delivery", $filter)) {
$billing_delivery = $db->escape($filter['billing_delivery']);
if($billing_delivery) {
$where .= " AND Billing.billing_delivery = '$billing_delivery'";
}
}

View File

@@ -32,7 +32,7 @@ class Contract extends mfBaseModel {
private $creator;
private $editor;
private function getLinks() {
$this->linkFrom = [];
$this->linkTo = [];

View File

@@ -350,9 +350,6 @@ class ContractController extends mfBaseController
}
}
/*
* TODO: alten Credit Vertag kündigen und neuen anlegen
*/
foreach (ContractLinkModel::search(['type' => "credit", 'contract_id' => $origin->id]) as $old_credit) {
// verlinkten Contract kündigen (wenn nicht schon gekündigt)
if ($old_credit->change_action == "recreate" && !$old_credit->contract->cancel_date) {
@@ -368,7 +365,6 @@ class ContractController extends mfBaseController
}
}
}
@@ -379,9 +375,12 @@ class ContractController extends mfBaseController
$this->redirect("Contract", "view", ['contract_id' => $id]);
}
/*
* bestehende Links übernehmen oder kündigen
*/
// create Journal
$journal = ContractjournalModel::create([
'contract_id' => $contract->id,
'type' => "contract_finished"
]);
$journal_id = $journal->save();
$this->layout()->setFlash("Contract erfolgreich fertiggestellt", "success");
$this->redirect("Contract", "view", ['contract_id' => $id]);
@@ -475,6 +474,8 @@ class ContractController extends mfBaseController
}
//var_dump($r->get());exit;
$contract_data = [];
$contract_data["owner_id"] = (int)$r->owner_id;
$contract_data["billingaddress_id"] = ($r->billingaddress_id) ? (int)$r->billingaddress_id : null;
@@ -489,11 +490,34 @@ class ContractController extends mfBaseController
$contract_data['price_nbe'] = (float)$r->price_nbe;
$contract_data['billing_period'] = (int)$r->billing_period;
$contract_data['billing_delay'] = (int)$r->billing_delay;
$contract_data['order_date'] = ($r->order_date) ? $this->dateToTimestamp($r->order_date) : null;
$contract_data['finish_date'] = ($r->finish_date) ? $this->dateToTimestamp($r->finish_date) : null;
$contract_data['cancel_date'] = ($r->cancel_date) ? $this->dateToTimestamp($r->cancel_date) : null;
$contract_data['note'] = $r->note;
if($r->order_date) {
$order_date = new DateTime("@" . $this->dateToTimestamp($r->order_date));
$order_date->setTimezone(new DateTimeZone("Europe/Vienna"));
$order_date->setTime(0, 0, 0);
$contract_data['order_date'] = $order_date->getTimestamp();
}
if($r->finish_date) {
$finish_date = new DateTime("@" . $this->dateToTimestamp($r->finish_date));
$finish_date->setTimezone(new DateTimeZone("Europe/Vienna"));
$finish_date->setTime(0, 0, 0);
$contract_data['finish_date'] = $finish_date->getTimestamp();
}
if($r->cancel_date) {
$cancel_date = new DateTime("@" . $this->dateToTimestamp($r->cancel_date));
$cancel_date->setTimezone(new DateTimeZone("Europe/Vienna"));
$cancel_date->setTime(0, 0, 0);
$contract_data['cancel_date'] = $cancel_date->getTimestamp();
}
//var_dump($contract_data);exit;
@@ -513,7 +537,7 @@ class ContractController extends mfBaseController
$this->layout()->setFlash("Bitte Produkt auswählen.", "error");
return $this->addAction();
}
if (!$contract_data['billing_period']) {
if (!in_array($contract_data['billing_period'], [0,1,12])) {
$this->layout()->setFlash("Bitte Rechnungsperiode auswählen.", "error");
return $this->addAction();
}

View File

@@ -15,6 +15,7 @@ class ContractModel {
public $product_external_id;
public $price = null;
public $price_setup = null;
public $vatrate = null;
public $price_nne = null;
public $price_nbe = null;
public $billing_delay = null;

View File

@@ -87,7 +87,7 @@ class ContractqueueModel {
$data["order_id"] = $order->id;
$data["orderproduct_id"] = $op->id;
$data["owner_id"] = $order->owner_id;
$data["billingaddress_id"] = $order->billingaddress_id;
$data["billingaddress_id"] = ($order->billingaddress_id) ? $order->billingaddress_id : $order->owner_id;
$data["termination_id"] = ($op->termination_id) ? $op->termination_id : null;
$data["product_id"] = $op->product_id;
$data["product_name"] = $product->name;

View File

@@ -0,0 +1,60 @@
<?php
class Invoice extends mfBaseModel {
private $positions;
public function getProperty($name) {
if($this->$name == null) {
if(!$this->id) {
return null;
}
if($name == "positions") {
$positions = InvoicepositionModel::search(["invoice_id" => $this->id]);
$this->positions = $positions;
return $this->positions;
}
if($name == "creator") {
$this->creator = mfValuecache::singleton()->get("Worker-id-".$this->create_by);
if($this->creator === null) {
$this->creator = new User($this->create_by);
if($this->creator->id) {
mfValuecache::singleton()->set("Worker-id-".$this->create_by, $this->creator);
}
}
return $this->creator;
}
if($name == "editor") {
$this->editor = mfValuecache::singleton()->get("Worker-id-".$this->edit_by);
if($this->editor === null) {
$this->editor = new User($this->edit_by);
if($this->editor->id) {
mfValuecache::singleton()->set("Worker-id-".$this->edit_by, $this->editor);
}
}
return $this->editor;
}
$classname = ucfirst($name);
$idfield = $name."_id";
$this->$name = mfValuecache::singleton()->get("mfObjectmodel-$name-".$this->$idfield);
if(!$this->$name) {
$this->$name = new $classname($this->$idfield);
}
if($this->$name->id) {
mfValuecache::singleton()->set("mfObjectmodel-$name-".$this->$name->id, $this->$name);
return $this->$name;
} else {
return null;
}
}
return $this->$name;
}
}

View File

@@ -0,0 +1,345 @@
<?php
class InvoiceController 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 indexAction() {
$this->layout()->setTemplate("Invoice/Index");
if ($this->request->resetFilter) {
unset($_SESSION[MFAPPNAME . '-Invoice-filter']);
}
$filter = [];
if (is_array($this->request->filter)) {
$filter = $this->request->filter;
$_SESSION[MFAPPNAME . '-Invoice-filter'] = $filter;
} else {
if (array_key_exists(MFAPPNAME . '-Invoice-filter', $_SESSION) && count($_SESSION[MFAPPNAME . '-Invoice-filter'])) {
$filter = $_SESSION[MFAPPNAME . '-Invoice-filter'];
}
}
$this->layout->set("filter", $filter);
$filter = $this->getPreparedFilter($filter);
// pagination defaults
$pagination = [];
$pagination['start'] = 0;
$pagination['count'] = 50;
$pagination['maxItems'] = 0;
if(is_numeric($this->request->s)) {
$pagination['start'] = intval($this->request->s);
}
//var_dump($filter);exit;
$pagination['maxItems'] = InvoiceModel::count($filter);
$billings = InvoiceModel::search($filter, $pagination);
$this->layout()->set("invoices", $billings);
$this->layout()->set("pagination", $pagination);
}
private function getPreparedFilter($filter)
{
$new_filter = [];
if(array_key_exists("customer", $filter)) {
if(array_key_exists("customer", $filter) && $filter["customer"]) {
$kunde = $this->db()->escape($filter['customer']);
if(!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = "";
$new_filter['add-where'] .= " AND (company like '%$kunde%' OR firstname like '%$kunde%' OR lastname like '%$kunde%' OR concat(firstname, ' ', lastname) like '%$kunde%' OR concat(lastname, ' ', firstname) like '%$kunde%')";
}
}
if(array_key_exists("address", $filter)) {
if(array_key_exists("address", $filter) && $filter["address"]) {
$search = $this->db()->escape($filter['address']);
if(!array_key_exists("add-where", $new_filter)) $new_filter["add-where"] = "";
$new_filter['add-where'] .= " AND (street like '%$search%' OR zip like '%$search%' OR city like '%$search%' OR country like '%$search%')";
}
}
if (is_array($filter) && count($filter)) {
foreach ($filter as $name => $value) {
$new_filter[$name] = $value;
}
}
return $new_filter;
}
protected function downloadInvoice() {
$id = $this->request->id;
if (!is_numeric($id) || !$id) {
$this->layout()->setFlash("Rechnung nicht gefunden", "error");
$this->redirect("Invoice");
}
$invoice = new Invoice($id);
if (!$invoice->id) {
$this->layout()->setFlash("Rechnung nicht gefunden", "error");
$this->redirect("Rechnung");
}
$vat = [];
foreach($invoice->positions as $p) {
if(!array_key_exists($p->vatrate, $vat)) {
$vat[$p->vatrate] = 0;
}
$vat[$p->vatrate] += $p->price_gross - $p->price;
}
$pdf_vars = [
"invoice" => $invoice,
"vat" => $vat
];
/*$this->layout()->setTemplate("Invoice/Print.pdf");
$this->layout()->set("invoice", $invoice);
$this->layout()->set("vat", $vat);
$this->layout()->set("ressourcePathPrefix", MFFANCYBASEURL."/");
return true;*/
$pdf = new PdfForm("Invoice/Print.pdf", $pdf_vars);
//$pdfpath = $pdf->render();
$pdf->download($invoice->invoice_number.".pdf");
}
protected function runInvoicingAction() {
$i = 0;
$p = 0;
// get pairs of owner_id and billingaddress_id, so each will be its own invoice
foreach(BillingModel::getInvoiceBaseData(['invoice_id' => null]) as $base) {
//var_dump($base); continue;
$owner_id = $base["owner_id"];
$billingaddress_id = $base["billingaddress_id"];
$billing_type = $base["billing_type"];
$billing_delivery = $base["billing_delivery"];
$bill_positions = [];
$credit_positions = [];
$billing_rows = BillingModel::search([
"owner_id" => $owner_id,
"billingaddress_id" => $billingaddress_id,
"billing_type" => $billing_type,
"billing_delivery" => $billing_delivery,
"invoice_id" => null]);
if(!count($billing_rows)) {
die("Keine nicht verrechneten Billing records für billingaddress_id");
}
//var_dump($owner_id, $billingaddress_id, $bills);exit;
$invoice_data = [];
foreach($billing_rows as $bill) {
$vatrate = $bill->vatrate;
$price = $bill->price;
$price_total = $bill->price * $bill->amount;
$price_gross = ($vatrate) ? $price_total + ($price_total / 100) * $vatrate : $price_total;
$price_setup = $bill->price_setup;
$price_setup_total = $bill->price_setup * $bill->amount;
$price_setup_gross = ($vatrate) ? $price_setup_total + ($price_setup_total / 100) * $vatrate : $price_setup_total;
$add_setup_position = ($price > 0 && $price_setup > 0);
$is_setup_only = ($price < 0.00001 && $price_setup > 0);
$position_data = [];
$position_data["billing_id"] = $bill->id;
$position_data["contract_id"] = $bill->contract_id;
$position_data["start_date"] = $bill->start_date;
$position_data["end_date"] = $bill->end_date;
$position_data["matchcode"] = $bill->matchcode;
$position_data["product_id"] = $bill->product_id;
$position_data["product_name"] = $bill->product_name;
$position_data["product_info"] = $bill->product_info;
$position_data["amount"] = $bill->amount;
$position_data["billing_period"] = $bill->billing_period;
if($is_setup_only) {
$this->log->debug("Contract ID ". $bill->contract_id." is setup only");
$position_data["price"] = $price_setup;
$position_data["price_total"] = $price_setup_total;
$position_data["price_gross"] = $price_setup_gross;
$position_data["vatrate"] = $vatrate;
$position_data["end_date"] = $position_data["start_date"];
} else {
$position_data["price"] = $price;
$position_data["price_total"] = $price_total;
$position_data["price_gross"] = $price_gross;
$position_data["vatrate"] = $vatrate;
}
$new_position = InvoicepositionModel::create($position_data);
$bill_positions[] = $new_position;
if($add_setup_position) {
$this->log->debug("Adding Setup Invoiceposition for Contract ID ". $bill->contract_id);
$setup_data = $position_data;
$setup_data["product_name"] = "Herstellungskosten ".$bill->product_name;
$setup_data["product_info"] = "";
$setup_data["price"] = $price_setup;
$setup_data["price_total"] = $price_setup * $bill->amount;
$setup_data["price_gross"] = $price_setup_gross;
$setup_data["vatrate"] = $vatrate;
$setup_data["end_date"] = $setup_data["start_date"];
$setup_position = InvoicepositionModel::create($setup_data);
$bill_positions[] = $setup_position;
}
/*if($bill->price >= 0 || $bill->price_setup >= 0) {
$bill_positions[] = InvoicepositionModel::create($position_data);
} else {
$credit_positions[] = InvoicepositionModel::create($position_data);;
}*/
$invoice_data["owner_id"] = $owner_id;
$invoice_data["billingaddress_id"] = $billingaddress_id;
$invoice_data["customer_number"] = $bill->customer_number;
$invoice_data["company"] = $bill->company;
$invoice_data["firstname"] = $bill->firstname;
$invoice_data["lastname"] = $bill->lastname;
$invoice_data["street"] = $bill->street;
$invoice_data["zip"] = $bill->zip;
$invoice_data["city"] = $bill->city;
$invoice_data["country"] = $bill->country;
$invoice_data["email"] = $bill->email;
$invoice_data["uid"] = $bill->uid;
$invoice_data["billing_type"] = $billing_type;
$invoice_data["billing_delivery"] = $billing_delivery;
$invoice_data["bank_account_bank"] = $bill->bank_account_bank;
$invoice_data["bank_account_owner"] = $bill->bank_account_owner;
$invoice_data["bank_account_iban"] = $bill->bank_account_iban;
$invoice_data["bank_account_bic"] = $bill->bank_account_bic;
$invoice_data["total"] = 0;
$invoice_data["total_gross"] = 0;
$invoice_data["total_vat"] = 0;
}
//var_dump($bill_positions, $credit_positions);exit;
// create Invoice
$invoice = InvoiceModel::create($invoice_data);
$invoice->startTransaction();
try {
if(!$invoice->save()) {
$invoice->rollbackTransaction();
die("Error saving Invoice");
}
$total_net = 0;
$total_gross = 0;
$total_vat = 0;
foreach($bill_positions as $position) {
// on error: rollback transaction
// add Invoice::id to Invoiceposition
$position->invoice_id = $invoice->id;
if(!$position->vatrate) {
$total_net += $position->price_total;
} else {
$total_vat += ($position->price_total / 100) * $position->vatrate;
$total_net += $position->price_total;
$total_gross += $position->price_gross;
}
// save Invoiceposition
if(!$position->save()) {
$invoice->rollbackTransaction();
die("Error saving Invoiceposition");
}
// ad Invoice::id to Bill
$bill = new Billing($position->billing_id);
if(!$bill->id) {
$invoice->rollbackTransaction();
die("Bill for Invoiceposition not found");
}
$bill->invoice_id = $invoice->id;
if(!$bill->save()) {
$invoice->rollbackTransaction();
die("error saving invoice_id to bill");
}
$p++;
}
$invoice->total = $total_net;
$invoice->total_gross = $total_gross;
$invoice->total_vat = $total_vat;
if(!$invoice->save()) {
$invoice->rollbackTransaction();
die("Error saving totals in Invoice");
}
// generate Invoice number
$new_num = InvoiceModel::getNextInvoiceNUmber();
$invoice->invoice_number = $new_num;
$invoice->invoice_date = date("U");
if(!$invoice->save()) {
$invoice->rollbackTransaction();
die("Error saving Invoice number and date");
}
// commit transaction
$invoice->commitTransaction();
$i++;
} catch (Exception $e) {
if($invoice) {
$invoice->rollbackTransaction();
}
die("Error saving Invoice!\n");
}
// Create Invoice PDF
// if billing_delivery == paper -> add to pdf collection
// else -> send by email
//var_dump($invoice);
//exit;
}
$this->layout()->setFlash("$i Rechnungen mit $p Rechnungspositionen erstellt");
$this->redirect("Invoice");
}
}

View File

@@ -0,0 +1,340 @@
<?php
class InvoiceModel {
public $invoice_number;
public $invoice_date;
public $billingaddress_id;
public $customer_number;
public $company;
public $firstname;
public $lastname;
public $street;
public $zip;
public $city;
public $country;
public $email;
public $uid;
public $billing_type;
public $billing_delivery;
public $bank_account_bank;
public $bank_account_owner;
public $bank_account_iban;
public $bank_account_bic;
public $total;
public $total_gross;
public $total_vat;
public $create_by;
public $edit_by;
public $create;
public $edit;
public static function create(Array $data) {
$model = new Invoice();
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 getNextInvoiceNUmber() {
$last_invoice_num = self::getLastInvoiceNumber();
if(!$last_invoice_num) {
return TT_FIRST_INVOICE_NUM;
}
$year_part = 0;
$num_part = 0;
$m = [];
if(preg_match('/^RN(\d+)-X(\d+)$/', $last_invoice_num, $m)) {
if(array_key_exists(1, $m)) {
$year_part = $m[1];
if(array_key_exists(2, $m)) {
$num_part = $m[2];
}
}
}
if(!$year_part || !$num_part) {
die("NO LAST INVOICE NUMBER!!!");
}
//var_dump($year_part, $num_part);exit;
if(date("Y") == $year_part) {
$new_year_part = $year_part;
$new_num_part = $num_part + 1;
} else {
$new_year_part = date("Y");
$new_num_part = 1;
}
$new_invoice_num = "RN$new_year_part-X".str_pad($new_num_part,"6", "0", STR_PAD_LEFT);
return $new_invoice_num;
}
public static function getLastInvoiceNumber() {
$last_invoice = self::getLast(["invoice_number" => true]);
if(!$last_invoice || !$last_invoice->invoice_number) {
return false;
}
return $last_invoice->invoice_number;
}
public static function getAll() {
$items = [];
$db = FronkDB::singleton();
$res = $db->select("Invoice", "*", "1 = 1 ORDER BY invoice_number");
if($db->num_rows($res)) {
while($data = $db->fetch_object($res)) {
$items[] = new Invoice($data);
}
}
return $items;
}
public static function getFirst($filter) {
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT * FROM Invoice
WHERE $where
ORDER BY invoice_number LIMIT 1";
//var_dump($sql);exit;
$res = $db->query($sql);
if($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new Invoice($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 Invoice
WHERE $where
ORDER BY invoice_number DESC LIMIT 1";
//mfLoghandler::singleton()->debug($sql);
$res = $db->query($sql);
if($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new Invoice($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 Invoice
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) {
//var_dump($filter);exit;
$items = [];
if(!$order) {
$order = "invoice_number ASC";
}
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT * FROM Invoice
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 Invoice($data);
}
}
return $items;
}
private static function getSqlFilter($filter) {
$where = "1=1 ";
$db = FronkDB::singleton();
//var_dump($filter);exit;
if(array_key_exists("id", $filter)) {
$id = $filter['id'];
if(is_numeric($id)) {
$where .= " AND Invoice.id like '%$id%'";
}
}
if(array_key_exists("invoice_number", $filter)) {
$invoice_number = $filter['invoice_number'];
if($invoice_number === true) {
$where .= " AND Invoice.invoice_number IS NOT NULL AND Invoice.invoice_number <> ''";
} elseif($invoice_number) {
$invoice_number = $db->escape($invoice_number);
$where .= " AND Invoice.invoice_number='$invoice_number'";
} elseif($invoice_number === null || $invoice_number === false) {
$where .= " AND Invoice.invoice_number IS NULL";
}
}
if(array_key_exists("invoice_date", $filter)) {
$invoice_date = $filter['invoice_date'];
if($invoice_date) {
$where .= " AND Invoice.invoice_date='$invoice_date'";
} elseif($invoice_date === null || $invoice_date === false) {
$where .= " AND Invoice.invoice_date IS NULL";
}
}
if(array_key_exists("billingaddress_id", $filter)) {
$Invoiceaddress_id = $filter['billingaddress_id'];
if(is_numeric($Invoiceaddress_id)) {
$where .= " AND Invoice.billingaddress_id=$Invoiceaddress_id";
}
}
if(array_key_exists("customer_number", $filter)) {
$customer_number = $filter['customer_number'];
if(is_numeric($customer_number)) {
$where .= " AND Invoice.customer_number LIKE $customer_number";
}
}
if (array_key_exists("company", $filter)) {
$company = FronkDB::singleton()->escape($filter["company"]);
if ($company) {
$where .= " AND company like '%$company%'";
}
}
if (array_key_exists("firstname", $filter)) {
$firstname = FronkDB::singleton()->escape($filter["firstname"]);
if ($firstname) {
$where .= " AND firstname like '%$firstname%'";
}
}
if (array_key_exists("lastname", $filter)) {
$lastname = FronkDB::singleton()->escape($filter["lastname"]);
if ($lastname) {
$where .= " AND lastname like '%$lastname%'";
}
}
if (array_key_exists("mergedName", $filter)) {
$name = FronkDB::singleton()->escape($filter["mergedName"]);
if ($name) {
$where .= " AND (CONCAT(firstname, ' ', lastname) like '%$name%' OR CONCAT(lastname, ' ', firstname) like '%$name%' )";
}
}
if (array_key_exists("street", $filter)) {
$street = FronkDB::singleton()->escape($filter["street"]);
if ($street) {
$where .= " AND street like '%$street%'";
}
}
if (array_key_exists("zip", $filter)) {
$zip = FronkDB::singleton()->escape($filter["zip"]);
if ($zip) {
$where .= " AND zip like '%$zip%'";
}
}
if (array_key_exists("city", $filter)) {
$city = FronkDB::singleton()->escape($filter["city"]);
if ($city) {
$where .= " AND city like '%$city%'";
}
}
if (array_key_exists("country", $filter)) {
$country = FronkDB::singleton()->escape($filter["country"]);
if ($country) {
$where .= " AND country like '%$country%'";
}
}
if (array_key_exists("email", $filter)) {
$email = FronkDB::singleton()->escape($filter["email"]);
if ($email) {
$where .= " AND email like '%$email%'";
}
}
if(array_key_exists("add-where", $filter)) {
$where .= " ".$filter['add-where'];
}
if(array_key_exists("Invoice_period", $filter)) {
$Invoice_period = $filter['Invoice_period'];
if(is_numeric($Invoice_period)) {
$where .= " AND Invoice.Invoice_period = $Invoice_period";
}
}
//var_dump($filter, $where);exit;
return $where;
}
}

View File

@@ -0,0 +1,34 @@
<?php
class Invoiceposition extends mfBaseModel {
public function __clone() {
$me = new User;
$me->loadMe();
$old_id = $this->id;
$this->id = null;
// cleanup data
$this->invoice_id = null;
$this->create_by = $me->id;
$this->edit_by = $me->id;
$this->create = null;
$this->edit = null;
$this->saved = 0;
$this->mode = "new";
$this->_old_data = new StdClass();
/*$this->save();
if($old_id == $this->id) {
$this->log->error("save() of cloned Contract $old_id failed!");
throw new Exception("Saving clone failed.");
}*/
//$this->log->debug("Cloned Invoiceposition $old_id");
}
}

View File

@@ -0,0 +1,165 @@
<?php
class InvoicepositionModel {
public $invoice_id;
public $billing_id;
public $contract_id;
public $start_date;
public $end_date;
public $matchcode;
public $product_id;
public $product_name;
public $product_info;
public $amount;
public $price;
public $price_total;
public $price_gross;
public $vatrate;
public $billing_period;
public $create_by;
public $edit_by;
public $create;
public $edit;
public static function create(Array $data) {
$model = new Invoiceposition();
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("Invoiceposition", "*", "1 = 1 ORDER BY invoice_id,contract_id,start_date,matchcode");
if($db->num_rows($res)) {
while($data = $db->fetch_object($res)) {
$items[] = new Invoiceposition($data);
}
}
return $items;
}
public static function getFirst($filter) {
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT * FROM Invoiceposition
WHERE $where
ORDER BY invoice_id,contract_id,start_date,matchcode LIMIT 1";
//var_dump($sql);exit;
$res = $db->query($sql);
if($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new Invoiceposition($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 Invoiceposition
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) {
//var_dump($filter);exit;
$items = [];
if(!$order) {
$order = "invoice_id,contract_id,start_date,matchcode ASC";
}
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT * FROM Invoiceposition
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 Invoiceposition($data);
}
}
return $items;
}
private static function getSqlFilter($filter) {
$where = "1=1 ";
$db = FronkDB::singleton();
//var_dump($filter);exit;
if(array_key_exists("id", $filter)) {
$id = $filter['id'];
if(is_numeric($id)) {
$where .= " AND Invoiceposition.id = $id";
}
}
if(array_key_exists("invoice_id", $filter)) {
$invoice_id = $filter['invoice_id'];
if(is_numeric($invoice_id)) {
$where .= " AND Invoiceposition.invoice_id=$invoice_id";
}
}
if(array_key_exists("add-where", $filter)) {
$where .= " ".$filter['add-where'];
}
//var_dump($filter, $where);exit;
return $where;
}
}

View File

@@ -232,6 +232,7 @@ class UserController extends mfBaseController
$user->permissions->canVoipnumbering = "false";
$user->permissions->canPreorder = "false";
$user->permissions->canOrder = "false";
$user->permissions->canBilling = "false";
$user->permissions->canFibu = "false";
$user->permissions->canStatistics = "false";

View File

@@ -10,16 +10,10 @@ final class CreateBilling extends AbstractMigration
if($this->getEnvironment() == "thetool") {
$table = $this->table("Billing");
$table->addColumn("invoice_id", "integer", ["null" => true, "default" => null]);
$table->addColumn("invoice_date", "date", ["null" => true, "default" => null]);
$table->addColumn("contract_id", "integer", ["null" => false]);
$table->addColumn("start_date", "date", ["null" => false]);
/*$table->addColumn("start_year", "integer", ["null" => false]);
$table->addColumn("start_month", "integer", ["null" => false]);
$table->addColumn("start_day", "integer", ["null" => false]);*/
$table->addColumn("end_date", "date", ["null" => false]);
/*$table->addColumn("end_year", "integer", ["null" => false]);
$table->addColumn("end_month", "integer", ["null" => false]);
$table->addColumn("end_day", "integer", ["null" => false]);*/
$table->addColumn("owner_id", "integer", ["null" => false]);
$table->addColumn("billingaddress_id", "integer", ["null" => false]);
$table->addColumn("customer_number", "integer", ["null" => false]);
$table->addColumn("company", "string", ["null" => true, "default" => null, "length" => 1024]);
@@ -44,6 +38,7 @@ final class CreateBilling extends AbstractMigration
$table->addColumn("amount", "decimal", ["null" => false, "precision" => 9, "scale" => 6]);
$table->addColumn("price", "decimal", ["null" => false, "precision" => 14, "scale" => 4]);
$table->addColumn("price_setup", "decimal", ["null" => false, "default" => 0, "precision" => 14, "scale" => 4]);
$table->addColumn("total_vat", "decimal", ["null" => false, "precision" => 14, "scale" => 4]);
$table->addColumn("billing_period", "integer", ["null" => false, "default" => 0]);
$table->addColumn("create_by", "integer", ["null" => false]);

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class CreateInvoiceTables extends AbstractMigration
{
public function up(): void
{
if($this->getEnvironment() == "thetool") {
$invoice = $this->table("Invoice");
$invoice->addColumn("invoice_number", "string", ["null" => true, "default" => null]); // RN2024-X000001
$invoice->addColumn("invoice_date", "integer", ["default" => 0]);
$invoice->addColumn("billingaddress_id", "integer", ["null" => false]);
$invoice->addColumn("customer_number", "integer", ["null" => false]);
$invoice->addColumn("company", "string", ["null" => true, "default" => null, "length" => 1024]);
$invoice->addColumn("firstname", "string", ["null" => true, "default" => null, "length" => 1024]);
$invoice->addColumn("lastname", "string", ["null" => true, "default" => null, "length" => 1024]);
$invoice->addColumn("street", "string", ["null" => false, "length" => 1024]);
$invoice->addColumn("zip", "string", ["null" => false, "length" => 1024]);
$invoice->addColumn("city", "string", ["null" => false, "length" => 1024]);
$invoice->addColumn("country", "string", ["null" => true, "default" => null, "length" => 1024]);
$invoice->addColumn("email", "string", ["null" => true, "default" => null, "length" => 1024]);
$invoice->addColumn("uid", "string", ["null" => true, "default" => null, "length" => 1024]);
$invoice->addColumn("billing_type", "enum", ["null" => false, "values" => "invoice,sepa"]);
$invoice->addColumn("billing_delivery", "enum", ["null" => false, "values" => "email,paper"]);
$invoice->addColumn("bank_account_bank", "string", ["null" => true, "default" => null, "length" => 255]);
$invoice->addColumn("bank_account_owner", "string", ["null" => true, "default" => null, "length" => 255]);
$invoice->addColumn("bank_account_iban", "string", ["null" => true, "default" => null, "length" => 255]);
$invoice->addColumn("bank_account_bic", "string", ["null" => true, "default" => null, "length" => 255]);
$invoice->addColumn("total", "decimal", ["null" => false, "precision" => 14, "scale" => 4]);
$invoice->addColumn("total_gross", "decimal", ["null" => false, "precision" => 14, "scale" => 4]);
$invoice->addColumn("vatrate", "decimal", ["null" => false, "default" => 0, "precision" => 6, "scale" => 2]);
$invoice->addColumn("create_by", "integer", ["null" => false]);
$invoice->addColumn("edit_by", "integer", ["null" => false]);
$invoice->addColumn("create", "integer", ["null" => false]);
$invoice->addColumn("edit", "integer", ["null" => false]);
$invoice->create();
$ip = $this->table("Invoiceposition");
$ip->addColumn("invoice_id", "integer", ["null" => true, "default" => null]);
$ip->addColumn("billing_id", "integer", ["null" => true, "default" => null]);
$ip->addColumn("contract_id", "integer", ["null" => false]);
$ip->addColumn("start_date", "date", ["null" => false]);
$ip->addColumn("end_date", "date", ["null" => true, "default" => null]);
$ip->addColumn("matchcode", "string", ["null" => true, "default" => null, "length" => 255]);
$ip->addColumn("product_id", "integer", ["null" => false]);
$ip->addColumn("product_name", "string", ["null" => false, "length" => 255]);
$ip->addColumn("product_info", "text", ["null" => true, "default" => null]);
$ip->addColumn("amount", "decimal", ["null" => false, "precision" => 9, "scale" => 6]);
$ip->addColumn("price", "decimal", ["null" => false, "precision" => 14, "scale" => 4]);
$ip->addColumn("price_total", "decimal", ["null" => false, "precision" => 14, "scale" => 4]);
$ip->addColumn("price_gross", "decimal", ["null" => false, "precision" => 14, "scale" => 4]);
$ip->addColumn("vatrate", "decimal", ["null" => false, "default" => 0, "precision" => 6, "scale" => 2]);
$ip->addColumn("billing_period", "integer", ["null" => false, "default" => 0]);
$ip->addColumn("create_by", "integer", ["null" => false]);
$ip->addColumn("edit_by", "integer", ["null" => false]);
$ip->addColumn("create", "integer", ["null" => false]);
$ip->addColumn("edit", "integer", ["null" => false]);
$ip->create();
}
if($this->getEnvironment() == "addressdb") {
}
}
public function down(): void
{
if($this->getEnvironment() == "thetool") {
$this->table("InvoicePosition")->drop()->save();
$this->table("Invoice")->drop()->save();
}
if($this->getEnvironment() == "addressdb") {
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class ContractAddVat extends AbstractMigration
{
public function up(): void
{
if($this->getEnvironment() == "thetool") {
$table = $this->table("Contract");
$table->addColumn("vatrate", "decimal", ["null" => false, "default" => 0, "precision" => 6, "scale" => 2, "after" => "price_setup"]);
$table->update();
}
if($this->getEnvironment() == "addressdb") {
}
}
public function down(): void
{
if($this->getEnvironment() == "thetool") {
$table = $this->table("Contract");
$table->removeColumn("vatrate");
$table->update();
}
if($this->getEnvironment() == "addressdb") {
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class WorkerPermissionAddBilling extends AbstractMigration
{
public function up(): void
{
if($this->getEnvironment() == "thetool") {
$table = $this->table("WorkerPermission");
$table->addColumn("canBilling", "enum", ["null" => false,"values" => 'false,true', "default" => "false", "after" => "canOrder"]);
$table->update();
}
if($this->getEnvironment() == "addressdb") {
}
}
public function down(): void
{
if($this->getEnvironment() == "thetool") {
$this->table("WorkerPermission")->removeColumn("canBilling")->save();
}
if($this->getEnvironment() == "addressdb") {
}
}
}

View File

@@ -57,6 +57,11 @@ $l['contract.relocation'] = "Umzug";
$l['contract.productchange'] = "Produktwechsel";
$l['contract.credit'] = "Gutschrift";
$l["billing_period.0"] = "Einmalig";
$l["billing_period.1"] = "Monatlich";
$l["billing_period.12"] = "Jährlich";
$l["billing_period.24"] = "Zweijährlich";
$l["billing_period.46"] = "Dreijährlich";
$l['cc.oesterreich'] = "AT";
$l['cc.oestereich'] = "AT";

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

174
public/css/sknx_print.css Normal file
View File

@@ -0,0 +1,174 @@
* {
font-family: "Open Sans";
}
html {
margin: 24pt;
height:100%;
}
body {
font-size:9pt;
height:100%;
margin-top: 50pt;
}
h2 {
font-size:13pt;
}
#adressen {
width:100%;
border-collapse: collapse;
font-size:10pt;
}
#adressen tr td {
width:40%;
vertical-align: top;
}
#adressen .left {
padding-top: 136pt;
width:55%;
}
#adressen .right {
width:45%;
}
.adresse {
border:none;
text-align:left;
line-height: 11pt;
}
.adresse div {
font-size:10pt;
}
.header_sknx div {
font-size:8pt;
line-height:10pt;
color:#505050;
}
.header_sknx div b {
font-size:11pt;
line-height:12pt;
color:#000;
}
table.position {
width:100%;
border-collapse: collapse;
font-size:9pt;
}
table tr,
table tr td {
page-break-inside: avoid !important;
break-inside: avoid !important;
}
table.position tr.head {
border-bottom: 1px solid #000;
}
table.position tr.even {
background-color:#f0f0f0;
}
table.position tr td,
table.position tr th {
text-align:center;
padding-left:4pt;
}
table.position tr td.name,
table.position tr th.name {
text-align:left;
width:50%;
}
table.position tr td.preis,
table.position tr th.preis {
text-align:right;
}
table.position tr td.freitext {
text-align:left;
padding-left:12pt;
}
.product-name {
font-weight: 600;
}
#total {
width:100%;
border-collapse: collapse;
font-size:10pt;
margin-bottom: 14pt;
page-break-inside: avoid;
}
#total td,
#total th {
width:50%;
}
#total th {
text-align:left;
}
#total td {
text-align:right;
font-weight:bold;
}
#total tr.ust td,
#total tr.ust th {
font-size: 9pt;
font-weight:normal;
}
#total tr.netto {
border-top:1px solid #000;
}
#total tr.brutto {
border-top:1px solid #000;
border-bottom:3px double #000;
}
#ausdruck td.beschriftung {
font-size: 11pt;
font-weight: bold;
text-align: left;
}
#ausdruck td.text {
font-size: 10pt;
font-weight: normal;
text-align: left;
}
.dontsplit {
page-break-inside:avoid;
}
.dontsplitafter {
page-break-after:avoid;
}
.dontsplitbefore {
page-break-before:avoid;
}
tbody::after {
content: ''; display: block;
page-break-after: always;
page-break-inside: avoid;
page-break-before: avoid;
}
.ml-2 {
margin-left: 8pt;
}
.ml-2 {
margin-left: 12pt;
}
.pl-2 {
padding-left: 8pt;
}
.pl-3 {
padding-left: 12pt;
}
.one-position {
vertical-align: top;
padding-bottom: 4pt;
}

View File

@@ -101,7 +101,7 @@ $tbms = 0;
$tbys = 0;
$tbs = 0;
foreach(BillingModel::search(["price>=" => 0]) as $bill) {
foreach(BillingModel::search(["price>=" => 0, "start_date" => "2024-06-01"]) as $bill) {
if($bill->billing_period == 1) {
$tbms += $bill->price;
}
@@ -129,7 +129,7 @@ $tbcms = 0;
$tbcys = 0;
$tbcs = 0;
foreach(BillingModel::search(["price<" => 0]) as $bill) {
foreach(BillingModel::search(["price<" => 0, "start_date" => "2024-06-01"]) as $bill) {
if($bill->billing_period == 1) {
$tbcms += $bill->price;
}