Merge branch 'fronkdev' into 'master'

Added Admin function mass prouductchange

See merge request fronk/thetool!1404
This commit is contained in:
Frank Schubert
2025-06-03 10:33:58 +00:00
8 changed files with 935 additions and 107 deletions
+31
View File
@@ -18,6 +18,37 @@ class AdminController extends mfBaseController {
$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() {
$this->layout()->setTemplate("Admin/CustomerStatistics");
+216 -27
View File
@@ -23,7 +23,7 @@ class Admin_MassProductchange {
if(method_exists($this, $method)) {
return $this->$method();
} 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() {
$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");
$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 [
$return = [
"template" => "Admin/MassProductchange/Index",
"redirect" => "",
"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;
}
}
+353
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) {
if($this->$name == null) {
+21 -7
View File
@@ -200,15 +200,15 @@ class ContractModel {
* @param Contract $contract
* @return bool|Contract
*/
public static function createCreditForContract($contract) {
public static function createCreditForContract($contract, $crediting_partner_id_override = null) {
$log = mfLoghandler::singleton();
$me = new User();
$me->loadMe();
if(!$contract->id) {
/*if(!$contract->id) {
$log->warning(__METHOD__."(): Invalid Contract object");
return false;
}
}*/
$product = $contract->product;
$owner = $contract->owner;
@@ -224,18 +224,26 @@ class ContractModel {
$crediting_partner_id = false;
$crediting_partner_rate = false;
if($crediting_partner_id_override) {
$crediting_partner_id = $crediting_partner_id_override;
}
$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($product_attribs["crediting_partner"]->value) {
$crediting_partner_id = $product_attribs["crediting_partner"]->value;
if(!$crediting_partner_id_override) {
if($product_attribs["crediting_partner"]->value) {
$crediting_partner_id = $product_attribs["crediting_partner"]->value;
}
}
if($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
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;
}
@@ -551,6 +559,8 @@ class ContractModel {
} elseif($termination_id) {
$termination_id = FronkDB::singleton()->escape($filter['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)) {
$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";
}elseif(is_array($billing_period) && count($billing_period)) {
$where .= " AND Contract.billing_period IN (".implode(",", $billing_period).")";
}
}
+7 -3
View File
@@ -50,14 +50,18 @@ class ProductModel {
if(!is_numeric($id) || !$id) {
throw new Exception("Invalid number", 400);
}
$item = [];
$item = null;
$db = FronkDB::singleton();
$res = $db->select("Product", "*", "id=$id LIMIT 1");
if($db->num_rows($res)) {
$data = $db->fetch_object($res);
$item = new Product($data);
$data = $db->fetch_object($res);
$item = new Product($data);
if(!$item->id) {
return null;
}
}
return $item;
}