Added Admin function mass prouductchange

This commit is contained in:
Frank Schubert
2025-06-03 12:33:00 +02:00
parent cd759272ac
commit 26a59fb588
8 changed files with 935 additions and 107 deletions

View File

@@ -17,65 +17,87 @@
<!-- end page title --> <!-- end page title -->
<div class="row"> <div class="row">
<div class="col-12 col-xl-10"> <div class="col-6 col-xl-5">
<h5>Backoffice</h5>
<div class="card">
<div class="card-body">
<div class="row col-12"> <div class="row">
<div><a href="<?=self::getUrl("Admin", "customerStatistics")?>">Kundenstatistiken</a></div> <div class="col-12">
</div> <h5>Backoffice</h5>
<div class="row col-12"> <div class="card">
<div><a href="<?=self::getUrl("Admin", "RtrReporting")?>">RTR Reporting</a></div> <div class="card-body">
<div class="row col-12">
<div><a href="<?=self::getUrl("Admin", "customerStatistics")?>">Kundenstatistiken</a></div>
</div>
<div class="row col-12">
<div><a href="<?=self::getUrl("Admin", "RtrReporting")?>">RTR Reporting</a></div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-12 col-xl-10"> <div class="col-12">
<h5>Network</h5> <h5>Network</h5>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="row col-12"> <div class="row col-12">
<div><a href="<?=self::getUrl("Admin", "createNetworkAddressForNetowner")?>">Fehlende NetworkAddress Objekte für Netowner anlegen</a></div> <div><a href="<?=self::getUrl("Admin", "createNetworkAddressForNetowner")?>">Fehlende NetworkAddress Objekte für Netowner anlegen</a></div>
</div>
</div>
</div>
</div>
</div> </div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-xl-10">
<h5>IVT</h5>
<div class="card">
<div class="card-body">
<div class="row col-12">
<div><a href="<?=self::getUrl("Admin", "ivtImportMatchProducts")?>">IVT Produkte zu thetool Produkte matchen</a></div>
</div>
<div class="row col-12 mt-2">
<div><a href="<?=self::getUrl("Admin", "ivtDownloadActiveProducts")?>">Produkte in Verwendung herunterladen</a></div>
</div>
</div>
</div>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-12 col-xl-10"> <div class="col-12">
<h5>IVT Contract Import</h5> <h5>IVT</h5>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="row col-12"> <div class="row col-12">
<div><a href="<?=self::getUrl("Admin", "ivtContractImport")?>" class="text-danger">IVT Contracts importieren</a></div> <div><a href="<?=self::getUrl("Admin", "ivtImportMatchProducts")?>">IVT Produkte zu thetool Produkte matchen</a></div>
</div>
<div class="row col-12 mt-2">
<div><a href="<?=self::getUrl("Admin", "ivtDownloadActiveProducts")?>">Produkte in Verwendung herunterladen</a></div>
</div>
</div>
</div>
</div>
</div> </div>
<div class="row col-12 mt-2">
<div><a href="<?=self::getUrl("Admin", "ivtCreditImport")?>" class="text-danger">IVT Gutschriften importieren</a></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<h5>IVT Contract Import</h5>
<div class="card">
<div class="card-body">
<div class="row col-12">
<div><a href="<?=self::getUrl("Admin", "ivtContractImport")?>" class="text-danger">IVT Contracts importieren</a></div>
</div>
<div class="row col-12 mt-2">
<div><a href="<?=self::getUrl("Admin", "ivtCreditImport")?>" class="text-danger">IVT Gutschriften importieren</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-6 col-xl-5">
<div class="row">
<div class="col-12">
<h5>Contracts</h5>
<div class="card">
<div class="card-body">
<div class="row col-12">
<div><a href="<?=self::getUrl("Admin", "massProductchange")?>">Masenproduktwechsel erstellen</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/footer.php"); ?> <?php include(realpath(dirname(__FILE__)."/../../$mfLayoutPackage")."/footer.php"); ?>

View File

@@ -32,10 +32,10 @@
<form method="post" action="<?=self::getUrl("Admin", "massProductchange", ["do" => "create"])?>"> <form method="post" action="<?=self::getUrl("Admin", "massProductchange", ["do" => "create"])?>">
<div class="form-group"> <div class="form-group">
<label for="old_product_id">Bisheriges Produkt</label> <label for="old_product_ids">Bisherige Produkte</label>
<select name="old_product_id" class="form-control select2"> <select name="old_product_ids[]" class="form-control select2" multiple="multiple">
<?php foreach(ProductModel::getAll() as $product): ?> <?php foreach(ProductModel::getAll() as $product): ?>
<option value="<?=$product->id?>"><?=$product->name?></option> <option value="<?=$product->id?>" <?=(isset($old_product_ids) && is_array($old_product_ids) && in_array($product->id, $old_product_ids)) ? "selected='selected'" : ""?>><?=$product->name?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
@@ -43,40 +43,96 @@
<div class="form-group"> <div class="form-group">
<label for="new_product_id">Neues Produkt</label> <label for="new_product_id">Neues Produkt</label>
<select name="new_product_id" class="form-control select2"> <select name="new_product_id" class="form-control select2">
<?php foreach(ProductModel::getAll() as $product): ?> <?php foreach(ProductModel::getActive() as $product): ?>
<option value="<?=$product->id?>"><?=$product->name?></option> <option value="<?=$product->id?>" <?=(isset($new_product_id) && $new_product_id && $new_product_id == $product->id) ? "selected='selected'" : ""?>><?=$product->name?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<small>Individuelle Produktnamen werden mit neuem Produktname überschrieben.</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="price_type">Neuer Preis</label> <label for="price_type">Neuer Preis</label>
<select name="price_type" id="price_type" class="form-control"> <select name="price_type" id="price_type" class="form-control">
<option value="keep">Bisherigen Preis beibehalten</option> <option value="keep" <?=(isset($price_type) && $price_type && $price_type == "keep") ? "selected='selected'" : ""?>>Bisherigen Preis beibehalten</option>
<option value="from_new_product">Preis vom neuen Produkt übernehmen</option> <option value="from_new_product" <?=(isset($price_type) && $price_type && $price_type == "from_new_product") ? "selected='selected'" : ""?>>Preis vom neuen Produkt übernehmen</option>
<option value="fixed">Fixen Preis definieren</option> <option value="fixed" <?=(isset($price_type) && $price_type && $price_type == "fixed") ? "selected='selected'" : ""?>>Fixen Preis definieren</option>
</select> </select>
</div> </div>
<div class="form-group hidden" id="fix_price_container"> <div id="from_new_product_container" class="hidden mb-3">
<label for="fix_price">Fixpreis</label> <div class="form-check">
<input type="text" name="fix_price" class="form-control" placeholder="Neuer Preis" value=""> <input class="form-check-input" type="checkbox" name="commit_setup_price" id="commit_setup_price" value="1" />
<label class="form-check-label" for="commit_setup_price">Setuppreis verrechnen</label>
</div>
</div>
<div id="fix_price_container" class="hidden">
<div class="row mb-3">
<div class="col-1"></div>
<div class="col-11">
<div class="form-group">
<label for="fix_price_setup">Fixpreis Setup</label>
<input type="text" name="fix_price_setup" class="form-control" placeholder="Neuer Setup Preis" value="">
<small>Wenn beim Produktwechsel ein Einmalentgelt verrechnet werden soll</small>
</div>
<div class="form-group">
<label for="fix_price">Fixpreis</label>
<input type="text" name="fix_price" class="form-control" placeholder="Neuer Preis" value="">
</div>
</div>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="linked_contracts_action">Verknüpfte Contracts</label> <label for="linked_contracts_action">Verknüpfte Contracts</label>
<select name="linked_contracts_action" id="linked_contracts_action" class="form-control"> <select name="linked_contracts_action" id="linked_contracts_action" class="form-control">
<option value="keep">übernehmen</option> <option value="keep" <?=(isset($linked_contracts_action) && $linked_contracts_action && $linked_contracts_action == "keep") ? "selected='selected'" : ""?>>übernehmen</option>
<option value="cancel">Kündigen</option> <option value="cancel" <?=(isset($linked_contracts_action) && $linked_contracts_action && $linked_contracts_action == "cancel") ? "selected='selected'" : ""?>>Kündigen</option>
</select> </select>
<small>Betrifft nur Zusatzprodukte; Provisionsgutschriften werden automatisch richtig zugeordnet.</small>
</div> </div>
<div class="form-group"> <div id="linked_contracts_cancel_container" class="hidden mb-3">
<div class="row mb-4">
<div class="col-1"></div>
<div class="col-11">
<div class="form-check">
<input class="form-check-input" type="radio" name="link_action_cancel_type" id="link_action_cancel_type_finish_date" value="finish_date" <?=(isset($link_action_cancel_type) && $link_action_cancel_type && $link_action_cancel_type == "finish_date") ? "checked='checked'" : ""?> />
<label class="form-check-label" for="link_action_cancel_type_finish_date">Kündigungsdatum = Fertigstellungsdatum des Produktwechsels</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="link_action_cancel_type" id="link_action_cancel_type_link_billing_period" value="link_billing_period" <?=(isset($link_action_cancel_type) && $link_action_cancel_type && $link_action_cancel_type == "link_billing_period") ? "checked='checked'" : ""?> />
<label class="form-check-label" for="link_action_cancel_type_link_billing_period">Kündigungsdatum = Nach aktueller Verrechnungsperiode (z.B. Ende des Monats)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="link_action_cancel_type" id="link_action_cancel_type_link_contract_term" value="link_contract_term" <?=(isset($link_action_cancel_type) && $link_action_cancel_type && $link_action_cancel_type == "link_contract_term") ? "checked='checked'" : ""?> />
<label class="form-check-label" for="link_action_cancel_type_link_contract_term">Kündigungsdatum = Ende der Vertragslaufzeit des verlinkten Contracts</label>
</div>
</div>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="exclude_termination_contracts" id="exclude_termination_contracts" value="1" <?=(isset($exclude_termination_contracts) && $exclude_termination_contracts) ? "checked='checked'" : ""?> />
<label class="form-check-label" for="exclude_termination_contracts">Nur Contracts ohne verknüpften Anschluss</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="no_provision_credits" id="no_provision_credits" value="1" <?=(isset($no_provision_credits) && $no_provision_credits) ? "checked='checked'" : ""?> />
<label class="form-check-label" for="no_provision_credits">Keine Provisionsgutschriften erstellen</label>
</div>
<div class="form-group mt-3">
<label for="change_date">Produktwechsel durchführen am</label> <label for="change_date">Produktwechsel durchführen am</label>
<input type="text" name="change_date" class="form-control" value=""> <input type="text" name="change_date" class="form-control datepicker" value="<?=(isset($change_date) && $change_date) ? $change_date : ""?>">
<small> = Fertigstellungsdatum vom neuen Contract</small>
</div> </div>
<button type="submit" name="preview" value="1" class="btn btn-primary">Vorschau</button> <button type="submit" name="preview" value="1" class="btn btn-primary">Vorschau</button>
<?php if(isset($change_contracts)): ?>
<button type="submit" name="commit" value="1" class="btn btn-danger">Produktwechsel jetzt erstellen</button>
<?php endif; ?>
</form> </form>
</div> </div>
</div> </div>
@@ -84,14 +140,155 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<?php if(isset($change_contracts)): ?>
<div class="row">
<div class="col">
<div class="card">
<div class="card-body">
<h3>Produktwechsel Vorschau</h3>
<p><?=count($change_contracts)?> Contracts zum Umstellen gefunden.</p>
<p>Hier werden alle Produktwechsel angezeigt, die erstellt werden.</p>
</div>
</div>
<?php foreach($change_contracts as $cc): ?>
<?php $oc = $cc["old_contract"]; ?>
<?php $nc = $cc["new_contract"]; ?>
<div class="card">
<table class="table table-sm m-3">
<tr>
<th></th>
<th>Alter Contract</th>
<th>Neuer Contract</th>
</tr>
<tr>
<th>Contract ID</th>
<td><a href="<?=self::getUrl("Contract", "view", ["contract_id" => $oc->id])?>" target="_blank"><?=$oc->id?></a></td>
<td>neu </td>
</tr>
<tr>
<th>Vertragsinhaber</th>
<td><a href="<?=self::getUrl("Address", "view", ["id" => $oc->owner_id])?>" target="_blank"><?=$oc->owner->getCompanyOrName()?></a></td>
<td><a href="<?=self::getUrl("Address", "view", ["id" => $nc->owner_id])?>" target="_blank"><?=$nc->owner->getCompanyOrName()?></a></td>
</tr>
<tr>
<th>Rechnungsempfänger</th>
<td><a href="<?=self::getUrl("Address", "view", ["id" => $oc->billingaddress_id])?>" target="_blank"><?=$oc->billingaddress->getCompanyOrName()?></a></td>
<td><a href="<?=self::getUrl("Address", "view", ["id" => $nc->billingaddress_id])?>" target="_blank"><?=$nc->billingaddress->getCompanyOrName()?></a></td>
</tr>
<tr>
<th>Matchcode</th>
<td><?=$oc->matchcode?></td>
<td><?=$nc->matchcode?></td>
</tr>
<tr>
<th>Produktname</th>
<th><?=$oc->product->name?></th>
<th><?=$nc->product->name?></th>
</tr>
<tr>
<th>Contract Produktname</th>
<td><?=$oc->product_name?></td>
<td><?=$nc->product_name?></td>
</tr>
<tr>
<th>Preis</th>
<th>€ <?=number_format(($oc->amount != 1) ? $oc->price * $oc->amount : $oc->price, 4, ",", ".")?></th>
<th>€ <?=number_format(($nc->amount != 1) ? $nc->price * $nc->amount : $nc->price, 4, ",", ".")?></th>
</tr>
<tr>
<th>Setup Preis</th>
<td>€ <?=number_format($oc->price_setup, 4, ",", ".")?><?=($oc->amount != 1) ? " (Gesamt: € ".number_format($oc->price_setup * $oc->amount, 4, ",", ".").")" : ""?></td>
<td><?=number_format($nc->price_setup, 4, ",", ".")?><?=($nc->amount != 1) ? " (Gesamt: € ".number_format($nc->price_setup * $nc->amount, 4, ",", ".").")" : ""?></td>
</tr>
</table>
<hr />
<div class="card-body">
<h5>Verknüpfungen</h5>
<?php if($cc["linked_contracts"]): ?>
<table class="table table-sm table-striped">
<tr>
<th class="text-right">Aktion</th>
<th>Contract ID</th>
<th>Matchcode</th>
<th>Produkt</th>
<th>Preis</th>
<th>Setup Preis</th>
</tr>
<?php foreach($cc["linked_contracts"] as $lc): ?>
<?php //var_dump($lc); ?>
<?php if(is_object($lc["link_from"]) && $lc["action"] != "create_credit" && $lc["link_from"]->id): ?>
<tr>
<td class="text-right">
<?php if($lc["action"] == "keep"): ?>
Verknüpfung mit
<?php elseif($lc["action"] == "cancel"): ?>
<span class="text-danger">Verlinkten Contract kündigen</span>
<?php endif; ?>
</td>
<td><?=$lc["link_from"]->id ?? "neu"?></td>
<td><?=$lc["link_from"]->matchcode?></td>
<td><?=$lc["link_from"]->product->name?></td>
<td class="<?=($lc["link_from"]->price < 0) ? "text-danger" : ""?>">€ <?=number_format(($lc["link_from"]->amount != 1) ? $lc["link_from"]->price * $lc["link_from"]->amount : $lc["link_from"]->price, 4, ",", ".")?></td>
<td>€ <?=number_format($lc["link_from"]->price_setup, 4, ",", ".")?><?=($lc["link_from"]->amount != 1) ? " (Gesamt: € ".number_format($lc["link_from"]->price_setup * $lc["link_from"]->amount, 4, ",", ".").")" : ""?></td>
</tr>
<?php endif; ?>
<?php if(is_object($lc["link_to"]) && $lc["link_to"]->product_id): ?>
<tr>
<td class="text-right">
<?php if($lc["action"] == "keep"): ?>
Verknüpfung mit
<?php elseif($lc["action"] == "create_credit"): ?>
neue Gutschrift an <strong><?=$lc["link_to"]->owner->getCompanyOrName()?></strong>
<?php elseif($lc["action"] == "cancel_credit"): ?>
<span class="text-danger">Gutschrift kündigen</span>
<?php endif; ?>
</td>
<td><?=$lc["link_to"]->id ?? "neu"?></td>
<td><?=$lc["link_to"]->matchcode?></td>
<td><?=$lc["link_to"]->product->name?></td>
<td class="<?=($lc["link_to"]->price < 0) ? "text-danger" : ""?>">€ <?=number_format(($lc["link_to"]->amount != 1) ? $lc["link_to"]->price * $lc["link_to"]->amount : $lc["link_to"]->price, 4, ",", ".")?></td>
<td>€ <?=number_format($lc["link_to"]->price_setup, 4, ",", ".")?><?=($lc["link_to"]->amount != 1) ? " (Gesamt: € ".number_format($lc["link_to"]->price_setup * $lc["link_to"]->amount, 4, ",", ".").")" : ""?></td>
</tr>
<?php endif; ?>
<?php if(is_object($lc["credit"])): ?>
<tr>
<td class="text-right">Gutschrift</td>
<td><?=$lc["credit"]->id ?? "neu"?></td>
<td><?=$lc["credit"]->matchcode?></td>
<td><?=$lc["credit"]->owner->getCompanyOrName()?></td>
<td class="<?=($lc["credit"]->price < 0) ? "text-danger" : ""?>">€ <?=number_format(($lc["credit"]->amount != 1) ? $lc["credit"]->price * $lc["credit"]->amount : $lc["credit"]->price, 4, ",", ".")?></td>
<td>€ <?=number_format($lc["credit"]->price_setup, 4, ",", ".")?><?=($lc["credit"]->amount != 1) ? " (Gesamt: € ".number_format($lc["credit"]->price_setup * $lc["credit"]->amount, 4, ",", ".").")" : ""?></td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
</table>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$(".select2").select2({ $(".select2").select2({
placeholder: "Bisheriges Produkt auswählen" placeholder: "Bisheriges Produkt auswählen",
closeOnSelect: false,
});
$('.datepicker').datepicker({
language: 'de',
format: "dd.mm.yyyy",
showWeekDays: true,
todayBtn: 'linked',
autoclose: true
}); });
$("#price_type").change(function() { $("#price_type").change(function() {
@@ -100,6 +297,20 @@
} else { } else {
$("#fix_price_container").hide(); $("#fix_price_container").hide();
} }
if($(this).val() == "from_new_product") {
$("#from_new_product_container").show();
} else {
$("#from_new_product_container").hide();
}
});
$("#linked_contracts_action").change(function() {
if($(this).val() == "cancel") {
$("#linked_contracts_cancel_container").show();
} else {
$("#linked_contracts_cancel_container").hide();
}
}); });
}); });
</script> </script>

View File

@@ -18,6 +18,37 @@ class AdminController extends mfBaseController {
$this->layout()->setTemplate("Admin/Index"); $this->layout()->setTemplate("Admin/Index");
} }
protected function massProductchangeAction() {
require_once(realpath(dirname(__FILE__)."/functions")."/MassProductchange.php");
$this->layout()->setTemplate("Admin/MassProductchange/Index");
$prodchange = new Admin_MassProductchange($this->request);
$response = $prodchange->runRequest();
foreach(["info", "success", "warning", "error"] as $level) {
if(array_key_exists($level, $response) && $response[$level]) {
$this->layout()->setFlash($response[$level], $level);
}
}
if($response["redirect"]) {
$this->redirect($response["redirect"]);
}
if($response["template"]) {
$this->layout()->setTemplate($response["template"]);
}
if(is_array($response["templateVars"]) && count($response["templateVars"])) {
foreach($response["templateVars"] as $key => $value) {
$this->layout()->set($key, $value);
}
}
}
protected function customerStatisticsAction() { protected function customerStatisticsAction() {
$this->layout()->setTemplate("Admin/CustomerStatistics"); $this->layout()->setTemplate("Admin/CustomerStatistics");

View File

@@ -23,7 +23,7 @@ class Admin_MassProductchange {
if(method_exists($this, $method)) { if(method_exists($this, $method)) {
return $this->$method(); return $this->$method();
} else { } else {
throw new Exception("Method not found", "404"); throw new Exception("Method ".htmlentities($method)." not found", "404");
} }
} }
} }
@@ -39,37 +39,226 @@ class Admin_MassProductchange {
public function createAction() { public function createAction() {
$this->log->debug("Create action called"); $this->log->debug("Create action called");
$this->log->debug($this->request->getPost()); $this->log->debug(print_r($this->request->get(), true));
$old_product_id = $this->request->getPost("old_product_id"); $return = [
$new_product_id = $this->request->getPost("new_product_id");
if(!$old_product_id || !$new_product_id) {
$this->flash[] = "Beide Produkte werden benötigt.";
return [
"template" => "Admin/MassProductchange/Index",
"redirect" => "",
"templateVars" => []
];
}
$contracts = ContractModel::searchActive(["product_id" => $old_product_id]);
if($this->request->preview) {
return [
"template" => "Admin/MassProductchange/Index",
"redirect" => "",
"templateVars" => ["contracts" => $contracts]
];
}
return [
"template" => "Admin/MassProductchange/Index", "template" => "Admin/MassProductchange/Index",
"redirect" => "", "redirect" => "",
"templateVars" => [] "templateVars" => []
]; ];
$r = $this->request;
$do_commit = $r->commit;
$linked_contracts_action = $r->linked_contracts_action;
$link_action_cancel_type = $r->link_action_cancel_type;
$exclude_termination_contracts = $r->exclude_termination_contracts;
$no_provision_credits = $r->no_provision_credits;
$price_type = $r->price_type;
$change_date = $r->change_date;
$old_product_ids = $r->old_product_ids;
$new_product_id = $r->new_product_id;
$return["templateVars"]["linked_contracts_action"] = $linked_contracts_action;
$return["templateVars"]["link_action_cancel_type"] = $link_action_cancel_type;
$return["templateVars"]["exclude_termination_contracts"] = $exclude_termination_contracts;
$return["templateVars"]["no_provision_credits"] = $no_provision_credits;
$return["templateVars"]["price_type"] = $price_type;
$return["templateVars"]["change_date"] = $change_date;
$return["templateVars"]["old_product_ids"] = $old_product_ids;
$return["templateVars"]["new_product_id"] = $new_product_id;
if(!$r->change_date) {
$return["error"] = "Fertigstellungsdatum ist erforderlich.";
return $return;
}
try {
$finish_date = DateTime::createFromFormat("d.m.Y", $change_date, new DateTimeZone("Europe/Vienna"));
$finish_date->setTime(0,0,0);
} catch (Exception $e) {
$return["error"] = "Ungültiges Fertigstellungsdatum";
return $return;
}
if(!is_array($old_product_ids) || !count($old_product_ids) || !$new_product_id) {
$return["error"] = "Beide Produkte werden benötigt.";
return $return;
}
$old_products = [];
foreach($old_product_ids as $product_id) {
$old_product = ProductModel::getOne($product_id);
if($old_product) $old_products[$old_product->id] = $old_product;
}
$new_product = ProductModel::getOne($new_product_id);
$contract_search = [
"product_id" => $old_product_ids,
"billing_period" => ">0",
"price>=" => "0",
"cancel_date" => null,
];
if($exclude_termination_contracts) {
$contract_search["termination_id"] = null;
}
$contracts = ContractModel::searchActive($contract_search, [
"order_by" => "product_id, matchcode",
"limit" => 1000,
]);
if(!$contracts) {
$return["error"] = "Keine passenden Verträge gefunden.";
return $return;
}
$commit_setup_price = false;
if($r->commit_setup_price) {
$commit_setup_price = true;
}
$results = [];
foreach($contracts as $contract) {
$new_price = $new_price_setup = $new_price_nne = $new_price_nbe = null;
if($price_type == "keep") {
// Preise von bestehendem Contract übernehmen.
// Setup Preis wird nicht nochmal verrechnet.
$new_price = $contract->price;
$new_price_setup = 0;
$new_price_nne = $contract->price_nne;
$new_price_nbe = $contract->price_nbe;
} elseif($price_type == "from_new_product") {
// Preise vom neuen Produkt übernehmen.
// Setup Preis kann verrechnet werden, wenn angegeben.
$new_price = $new_product->price;
$new_price_setup = 0;
$new_price_nne = $new_product->price_nne;
$new_price_nbe = $new_product->price_nbe;
if($commit_setup_price) {
$new_price_setup = $new_product->price_setup;
}
} elseif($price_type == "fixed") {
$new_price = Layout::commaToDot(trim($r->fix_price));
$new_price_setup = Layout::commaToDot(trim($r->fix_price_setup));
}
if($old_products[$contract->product_id]->price > 0.0000 && $contract->price == 0.0000) {
// if old contract has a discount price of zero, keep it that way
$new_price = 0;
$new_price_setup = 0;
$new_price_nne = 0;
}
$contract_data = [];
$contract_data['product_id'] = $new_product->id;
$contract_data['product_name'] = trim($new_product->name);
$contract_data['product_info'] = trim($contract->product_info);
$contract_data['matchcode'] = trim($contract->matchcode);
$contract_data['termination_id'] = $contract->termination_id;
$contract_data['amount'] = $contract->amount;
$contract_data['price'] = $new_price;
$contract_data['price_setup'] = $new_price_setup;
$contract_data['price_nne'] = $new_price_nne;
$contract_data['price_nbe'] = $new_price_nbe;
$contract_data["finish_date"] = $finish_date->format("d.m.Y");
$contract_data['note'] = trim($r->note);
// get linked contracts
$link_data = [];
foreach(ContractLinkModel::includesContractId($contract->id, ["type" => ["link", "credit"]]) as $link) {
if($link->type == "credit") {
$link_data[$link->id] = [
"action" => $linked_contracts_action,
"cancel_date" => null,
];
continue;
}
$link_cancel_date = null;
if($linked_contracts_action == "cancel") {
// determine requested cancel date of linked contract
if($link_action_cancel_type == "finish_date") {
$lcd = clone $finish_date;
$lcd->modify("-1 day");
$link_cancel_date = $lcd->format("d.m.Y");
}
if($link_action_cancel_type == "link_contract_term") {
// Ende der Vertragslaufzeit
$link_cancel_date = $link->contract->getRegularCanceldate("d.m.Y");
}
if($link_action_cancel_type == "link_billing_period") {
// Ende der aktuellen Verrechnungsperiode
$link_cancel_date = $link->contract->getNextBillingPeriodEnd("d.m.Y");
}
}
$link_data[$link->id] = [
"action" => $linked_contracts_action,
"cancel_date" => $link_cancel_date,
];
}
//if(!count($link_data)) continue;
//var_dump($link_data);exit;
try {
$productchange_result = $contract->createProductchangeContract($contract_data, $link_data, [
"link_action" => $linked_contracts_action,
"link_action_cancel_type" => $link_action_cancel_type,
"no_provision_credits" => $no_provision_credits,
], [
"ignore_missing_term" => true,
]);
} catch (\Exception $e) {
$return["error"] = "Fehler beim Erstellen des Produktwechsels für Vertrag {$contract->id}: " . $e->getMessage();
return $return;
}
if(false && count($results) > 0) {
var_dump($productchange_result);
foreach($productchange_result["linked_contracts"] as $l) {
echo "Link FROM:";
var_dump($l["link_from"]);
echo "Link TO:";
var_dump($l["link_to"]);
}
exit;
}
$results[] = $productchange_result;
}
//var_dump($results);exit;
if($do_commit) {
// commit all productchanges
foreach($results as $result) {
$old_contract = $result["old_contract"];
$new_contract = $result["new_contract"];
$links = $result["linked_contracts"];
if(!$old_contract->commitProductchangeContract($new_contract, $links)) {
$return["error"] = "Fehler beim Commit des Produktwechsels für Vertrag {$old_contract->id}";
return $return;
}
}
$return["success"] = count($results)." Produktwechsel erfolgreich durchgeführt.";
return $return;
}
$return["templateVars"]["contracts"] = $contracts;
$return["templateVars"]["change_contracts"] = $results;
return $return;
} }
} }

View File

@@ -535,6 +535,359 @@ class Contract extends mfBaseModel {
} }
public function createProductchangeContract($new_product_data, $links = [], $options = []) {
$contract_cancel_date = null;
$me = new User();
$me->loadMe();
$ignore_missing_term = $options["ignore_missing_term"] ?? false;
$no_provision_credits = $options["no_provision_credits"] ?? false;
$new_contract = clone $this;
$linked_contracts = [];
foreach($new_product_data as $field => $value) {
$new_contract->$field = $value;
}
/*
* Product check
*/
$product = new Product($new_contract->product_id);
if (!$product->id) {
throw new Exception("Produkt nicht gefunden.");
}
$new_contract->product_external = $product->external;
$new_contract->product_external_id = $product->external_id;
$new_contract->sla_id = $product->sla_id;
// finish date
if (array_key_exists("finish_date", $new_product_data) && $new_product_data["finish_date"]) {
try {
$finish_date = DateTime::createFromFormat("d.m.Y", $new_product_data["finish_date"], new DateTimeZone("Europe/Vienna"));
} catch (Exception $e) {
throw new Exception("Ungültiges Kündigungsdatum");
}
$finish_date->setTime(0, 0, 0);
$new_contract->finish_date = $finish_date->getTimestamp();
$contract_cancel_date = clone($finish_date);
$contract_cancel_date->modify("-1 day");
$contract_cancel_date->setTime(23, 59, 59);
}
// termination_id
$require_term = false;
if (array_key_exists(TT_ATTRIB_TERMINATION_REQUIRED_NAME, $product->attributes) && $product->attributes[TT_ATTRIB_TERMINATION_REQUIRED_NAME]->value == 1) {
$require_term = true;
$termination = new Termination($this->termination_id);
if ($ignore_missing_term && (!$new_product_data['termination_id'] || !$termination->id)) {
throw new Exception("Produkt erfordert Anschluss.");
}
} else {
$new_contract->termination_id = null;
}
// lookup credit contract and if it's missing in $r->links
if (!$me->is("Admin")) {
$credit_links = ContractLinkModel::includesContractId($this->id, ["type" => "credit"]);
foreach($credit_links as $credit_link) {
if(!$credit_link->contract->isFinished()) continue;
$links[$credit_link->id] = [
"action" => "keep"
];
}
}
// TODO: Contractconfig übernehmen
if ($contract_cancel_date) {
$this->cancel_date = $contract_cancel_date->getTimestamp();
$this->edit_by = $me->id;
//$contract->save();
}
/*
* Hndle Linked Contracts
*/
$handled_credit_contract = false;
if (is_array($links) && count($links)) {
foreach ($links as $link_id => $link_data) {
$action = $link_data["action"];
$cancel_date = false;
if ($link_data["cancel_date"]) {
try {
$cancel_date = DateTime::createFromFormat("d.m.Y", $link_data["cancel_date"], new DateTimeZone("Europe/Vienna"));
} catch (Exception $e) {
throw new Exception("Ungültiges Kündigungsdatum");
}
}
$old_link = new ContractLink($link_id);
if (!$old_link->id) continue;
// check if link contains this contract
if ($old_link->contract_id == $this->id) {
$origin_id = $old_link->origin_contract_id;
$link_contract_id = $old_link->contract_id;
$new_link_contract_id = $new_contract->id;
$new_link_origin_id = $old_link->origin_contract_id;
} elseif ($old_link->origin_contract_id == $this->id) {
$origin_id = $old_link->contract_id;
$link_contract_id = $old_link->origin_contract_id;
$new_link_contract_id = $old_link->contract_id;
$new_link_origin_id = $new_contract->id;
} else {
continue;
}
if ($action != "cancel" && $old_link->type != "credit") {
/*$new_link = ContractLinkModel::create([
'contract_id' => $new_link_contract_id,
'origin_contract_id' => $new_link_origin_id,
'type' => $old_link->type,
]);*/
$link_from_id = $new_link_contract_id;
if(!$link_from_id) $link_from_id = $new_link_origin_id;
$linked_contracts[] = [
"action" => "keep",
"link_from" => new Contract($link_from_id),
"link_to" => "new_contract",
"credit" => null,
];
/*if($new_contract->owner_id == 3997) {
var_dump($link_from_id);
var_dump($new_link_origin_id, $new_link_contract_id, $old_link);exit;
}*/
/*if (!$new_link->save()) {
throw new Exception("Konnte neuen Link nicht speichern");
}*/
}
if ($action == "cancel") {
if ($cancel_date && $contract_cancel_date) {
// insert cancel_date in old contract
$lc = new Contract($origin_id);
$lc->cancel_date = $cancel_date->getTimestamp();
$linked_contracts[] = [
"action" => "cancel",
"link_from" => $lc,
"link_to" => null,
"credit" => null,
];
//$lc->save();
} else {
// leave cancellation for later (when finishing upgrade)
$old_link->change_action = "cancel";
if (!$old_link->save()) {
$this->layout()->setFlash("Konnte alten Link nicht speichern", "warn");
}
}
}
if (!$no_provision_credits && $old_link->type == "credit" && $action == "keep") {
$handled_credit_contract = true;
// XXX - if we have finish date then recreate credit contract right now
if ($contract_cancel_date) {
$new_credit = ContractModel::createCreditForContract($new_contract);
if(!$new_credit) {
// new product does not require Credit Contract
// or if contract was imported from IVT, credit contract cannot be created because of missing termination_id
// so creating is manually
if($this->imported_from = "ivt") {
// get crediting partner
$credit_contract = new Contract($origin_id);
if($credit_contract->price > 0) {
// in case contract and origin are swapped
$credit_contract = new Contract($link_contract_id);
if($credit_contract->price > 0) {
$this->log->warn(__METHOD__.": Unable to create credit contract for product change, because can't determine original crediting contract.");
}
}
if($credit_contract->price < 0) {
$crediting_partner_id = $credit_contract->owner_id;
$new_credit = ContractModel::createCreditForContract($new_contract, $crediting_partner_id);
/*if($new_contract->owner_id != 727) {
var_dump($new_credit);
exit;
}*/
}
}
}
//$new_credit->save();
// create journal for credit
/*$journal = ContractjournalModel::create(['contract_id' => $new_credit->id,
'type' => "created_from",
'value' => "productchange",
'text' => "Produkt-/Standortwechsel von Contract ID " . $new_link_origin_id]);
$journal->save();*/
$this->log->debug(print_r($new_credit, true));
// set cancel date for old credit
$old_credit = new Contract($origin_id);
$old_credit->cancel_date = $contract_cancel_date->getTimestamp();
//$old_credit->save();
// create link to new credit contract
/*$link = ContractLinkModel::create(["contract_id" => $new_credit->id,
"origin_contract_id" => $new_contract->id,
"type" => "credit"]);
$link->save();*/
// create upgrade link from old to new credit contract
/*$link = ContractLinkModel::create(["contract_id" => $new_credit->id,
"origin_contract_id" => $origin_id,
"type" => "upgrade"]);
$link->save();*/
$linked_contracts[] = [
"action" => "create_credit",
"link_from" => $new_contract,
"link_to" => $new_credit,
];
$linked_contracts[] = [
"action" => "cancel_credit",
"link_from" => $new_contract,
"link_to" => $old_credit,
];
} else {
$old_link->change_action = "recreate";
//$old_link->save();
}
}
//var_dump($new_link);exit;
}
}
if(!$no_provision_credits && !$handled_credit_contract) {
// there was no credit contract linked before, so try creating one
$credit_contract = ContractModel::createCreditForContract($new_contract);
if(is_object($credit_contract)) {
$linked_contracts[] = [
"action" => "create_credit",
"link_from" => $new_contract,
"link_to" => $credit_contract,
];
} else {
$this->log->warn("Konnte keinen Credit Contract für Produktwechsel erstellen.");
}
}
return [
"new_contract" => $new_contract,
"old_contract" => $this,
"linked_contracts" => $linked_contracts,
];
}
/*
* To commit the product change, old and new contract only need to be saved. They are prepared by createProductchangeContract() already
* Links need to be created depending on action:
* - for action keep: create ContractLink from link_from to link_to
* - for action cancel: save link_from (has cancel_date already)
* - for action create_credit: save link_to (new credit contract), link link_to (credit) to link_from (new contract)
* - for action cancel_credit: save link_to (old credit to cancel), ignore link_from (new contract)
*
*
*
*/
public function commitProductchangeContract(\Contract $new_contract, $links = []) {
if(!count($links)) return true;
if($new_contract->owner_id==3476) return true;
$this->startTransaction();
if(!$this->save()) {
$this->rollbackTransaction();
return false;
}
if(!$new_contract->save()) {
$this->rollbackTransaction();
return false;
}
// TODO
//$new_contract->copyConfigFromContractId($this->id);
$link = ContractLinkModel::create(['contract_id' => $new_contract->id,
'origin_contract_id' => $this->id,
'type' => "upgrade"]);
$link->save();
$journal = ContractjournalModel::create(['contract_id' => $new_contract->id,
'type' => "created_from",
'value' => "productchange",
'text' => "Produkt-/Standortwechsel von Contract ID " . $this->id]);
$journal->save();
foreach($links as $link) {
//var_dump($link);exit;
$origin = $link["link_from"];
$to = $link["link_to"];
$action = $link["action"];
if($action == "keep") {
if($to == "new_contract") {
$to = $new_contract;
}
//var_dump($origin, $to);exit;
if(!is_object($origin) || !is_object($to)) continue;
$new_link = ContractLinkModel::create([
'contract_id' => $to->id,
'origin_contract_id' => $origin->id,
'type' => "link",
]);
//var_dump($link, $new_link);exit;
if(!$new_link->save()) {
$this->rollbackTransaction();
return false;
}
}
}
$this->commitTransaction();
return true;
var_dump($new_link);exit;
var_dump($this);
var_dump($new_contract);
//var_dump($links);
//var_dump($links);
foreach($links as $link) {
echo var_dump($link["action"]) . "\n<br />";
echo "LINK FROM";
var_dump($link["link_from"]);
echo "LINK TO";
var_dump($link["link_to"]);
}
exit;
}
public function getProperty($name) { public function getProperty($name) {
if($this->$name == null) { if($this->$name == null) {

View File

@@ -200,15 +200,15 @@ class ContractModel {
* @param Contract $contract * @param Contract $contract
* @return bool|Contract * @return bool|Contract
*/ */
public static function createCreditForContract($contract) { public static function createCreditForContract($contract, $crediting_partner_id_override = null) {
$log = mfLoghandler::singleton(); $log = mfLoghandler::singleton();
$me = new User(); $me = new User();
$me->loadMe(); $me->loadMe();
if(!$contract->id) { /*if(!$contract->id) {
$log->warning(__METHOD__."(): Invalid Contract object"); $log->warning(__METHOD__."(): Invalid Contract object");
return false; return false;
} }*/
$product = $contract->product; $product = $contract->product;
$owner = $contract->owner; $owner = $contract->owner;
@@ -224,18 +224,26 @@ class ContractModel {
$crediting_partner_id = false; $crediting_partner_id = false;
$crediting_partner_rate = false; $crediting_partner_rate = false;
if($crediting_partner_id_override) {
$crediting_partner_id = $crediting_partner_id_override;
}
$product_attribs = $product->attributes; $product_attribs = $product->attributes;
if(is_array($product_attribs) && array_key_exists("crediting_partner", $product_attribs) && $product_attribs["crediting_partner"] && is_object($product_attribs["crediting_partner"])) { if(is_array($product_attribs) && array_key_exists("crediting_partner", $product_attribs) && $product_attribs["crediting_partner"] && is_object($product_attribs["crediting_partner"])) {
if($product_attribs["crediting_partner"]->value) { if(!$crediting_partner_id_override) {
$crediting_partner_id = $product_attribs["crediting_partner"]->value; if($product_attribs["crediting_partner"]->value) {
$crediting_partner_id = $product_attribs["crediting_partner"]->value;
}
} }
if($product_attribs["crediting_rate"]->value) { if($product_attribs["crediting_rate"]->value) {
$crediting_partner_rate = str_replace(",", ".",$product_attribs["crediting_rate"]->value); $crediting_partner_rate = str_replace(",", ".",$product_attribs["crediting_rate"]->value);
} }
} }
//var_dump($crediting_partner_rate, $product);
// or from netowner if anschluss product // or from netowner if anschluss product
if(!$crediting_partner_id && $contract->termination_id) { if(!$crediting_partner_id_override && !$crediting_partner_id && $contract->termination_id) {
$crediting_partner_id = $contract->termination->building->network->owner_id; $crediting_partner_id = $contract->termination->building->network->owner_id;
} }
@@ -551,6 +559,8 @@ class ContractModel {
} elseif($termination_id) { } elseif($termination_id) {
$termination_id = FronkDB::singleton()->escape($filter['termination_id']); $termination_id = FronkDB::singleton()->escape($filter['termination_id']);
$where .= " AND Contract.termination_id = $termination_id"; $where .= " AND Contract.termination_id = $termination_id";
} elseif($termination_id === null || $termination_id === false || $termination_id == 0) {
$where .= " AND (Contract.termination_id = 0 OR Contract.termination_id IS NULL)";
} }
} }
@@ -624,8 +634,12 @@ class ContractModel {
if(array_key_exists("billing_period", $filter)) { if(array_key_exists("billing_period", $filter)) {
$billing_period = $filter['billing_period']; $billing_period = $filter['billing_period'];
if(is_numeric($billing_period)) { if($billing_period == ">0") {
$where .= " AND Contract.billing_period > 0";
}elseif(is_numeric($billing_period)) {
$where .= " AND Contract.billing_period=$billing_period"; $where .= " AND Contract.billing_period=$billing_period";
}elseif(is_array($billing_period) && count($billing_period)) {
$where .= " AND Contract.billing_period IN (".implode(",", $billing_period).")";
} }
} }

View File

@@ -50,14 +50,18 @@ class ProductModel {
if(!is_numeric($id) || !$id) { if(!is_numeric($id) || !$id) {
throw new Exception("Invalid number", 400); throw new Exception("Invalid number", 400);
} }
$item = []; $item = null;
$db = FronkDB::singleton(); $db = FronkDB::singleton();
$res = $db->select("Product", "*", "id=$id LIMIT 1"); $res = $db->select("Product", "*", "id=$id LIMIT 1");
if($db->num_rows($res)) { if($db->num_rows($res)) {
$data = $db->fetch_object($res); $data = $db->fetch_object($res);
$item = new Product($data); $item = new Product($data);
if(!$item->id) {
return null;
}
} }
return $item; return $item;
} }

View File

@@ -78,4 +78,8 @@ class mfRequest {
public function __get($name) { public function __get($name) {
return $this->get($name); return $this->get($name);
} }
public function __isset($name) {
return $this->isset($name);
}
} }