Ver código fonte

Merge branch 'master' of ssh://biuro.biall-net.pl:2222/plabudda/se

a.binder 6 anos atrás
pai
commit
fd6a6938d8

+ 1 - 1
SE/schema/ant-object/default_db.DEALS_TABLE/DEALS_TABLE/DEALS_TABLE.xsd

@@ -189,7 +189,7 @@
             id="DEALS_TABLE___d4e6242-1_S_ADDRESS_"/>
          <xs:element name="RODZAJ_DZIALANIA_HANDLOWEGO" type="xs:string"
             id="DEALS_TABLE___d4e6243-1_RODZAJ_DZI"/>
-         <xs:element name="L2_HANGUP_FROM" type="xs:token" id="DEALS_TABLE___d4e6244-1_L2_HANGUP_"/>
+         <xs:element name="L2_HANGUP_FROM" type="xs:date" id="DEALS_TABLE___d4e6244-1_L2_HANGUP_"/>
          <xs:element name="M_DIST_FILES" type="xs:string" id="DEALS_TABLE___d4e6245-1_M_DIST_FIL"/>
          <xs:element name="alias_OFFERS_GROUPS__S_ALIAS" type="xs:string"
             id="DEALS_TABLE___d4e6246-1_alias_OFFE"/>

+ 14 - 0
SE/se-lib/AntAclBase.php

@@ -367,6 +367,20 @@ class AntAclBase extends Core_AclBase {
 		if ('p5:enum' === $xsdType) return true;
 		return false;
 	}
+	public function isDateField($fieldName) {
+		$xsdType = $this->getXsdFieldType($fieldName);
+		switch ($xsdType) {
+			case "xsd:date": return true;
+			default: return false;
+		}
+	}
+	public function isDateTimeField($fieldName) {
+		$xsdType = $this->getXsdFieldType($fieldName);
+		switch ($xsdType) {
+			case "xsd:dateTime": return true;
+			default: return false;
+		}
+	}
 
 	public function canCreateField($fieldName) {
 		try {

+ 6 - 0
SE/se-lib/Route/Storage/AclReinstall.php

@@ -588,6 +588,12 @@ class Route_Storage_AclReinstall extends RouteBase {
 			// case 'p5:www_link':
 			// case 'xsd:gYear':
 			// case 'xsd:hexBinary':
+
+			case 'p5:link': {
+				DBG::log("Skipped create p5:link field '{$fieldInfo['fieldNamespace']}'");
+				return null;
+			}
+
 			default: {
 				DBG::log($fieldInfo, 'array', "TODO: _makeFieldStructure '{$fieldInfo['xsdType']}' (SystemObject/fieldInfo)"); // $fieldInfo = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ])['field'];
 				throw new Exception("Not implemented type '{$fieldInfo['xsdType']}'");

+ 7 - 1
SE/se-lib/Route/Test/ConvertDitaRelatedFeature.php

@@ -3,13 +3,19 @@
 Lib::loadClass('RouteBase');
 Lib::loadClass('XML');
 
+// @see Route_Test_XslRecurse for example use of php function inside xsl
+// - TODO: input file should have another extension, it is not correct dita file, it has tags RelatedFeature
+// IDEA: make object (DitaRelatedFeatureParser) with state to parse input file (dita RelatedFeature tmpl)
+// - at the beginning od tag RelatedFeature run php function to set current xpath in Parse object
+// - at the end od tag RelatedFeature run php function to unset current xpath in Parse object (go up in doc tree)
+
 class Route_Test_ConvertDitaRelatedFeature extends RouteBase {
 
 	function defaultAction() {
 		UI::layout( [ $this, 'defaultView' ], [ 'showMenu' => false ] );
 	}
 	function defaultView() {
-		$inputPath = APP_PATH_ROOT . DS . 'schema/ant-url_action/default_db.in7_dziennik_koresp/test-druk/IN7_DZIENNIK_KORESP.dita';
+		$inputPath = APP_PATH_ROOT . DS . 'schema/ant-url_action/default_db.in7_dziennik_koresp/test-druk/IN7_DZIENNIK_KORESP.dita'; // TODO: it is not correct dita file - has tags RelatedFeature
 		$inputBody = file_get_contents($inputPath);
 		DBG::nicePrint(htmlspecialchars($inputBody), "input");
 		DBG::nicePrint(null, "memory_get_usage: " . round(memory_get_usage() / 1024) . " KB");

+ 23 - 23
SE/se-lib/Route/Test/XslRecurse.php

@@ -12,48 +12,48 @@ class Route_Test_XslRecurse extends RouteBase {
 		Lib::loadXslFunction('testHtmlContent');
 		$xml = "
 			<root>
-			 <item typeName=\"default_db:TEST_PERMS\">
-			  <featureId>TEST_PERMS.1</featureId>
-			 </item>
-			 <item typeName=\"default_db:TEST_PERMS\">
-			  <featureId>TEST_PERMS.99999</featureId>
-			 </item>
-			 <item typeName=\"default_db__x3A__TEST_PERMS:TestPermsAnt\">
-			  <featureId>TestPermsAnt.1</featureId>
-			 </item>
+				<item typeName=\"default_db:TEST_PERMS\">
+					<featureId>TEST_PERMS.1</featureId>
+				</item>
+				<item typeName=\"default_db:TEST_PERMS\">
+					<featureId>TEST_PERMS.99999</featureId>
+				</item>
+				<item typeName=\"default_db__x3A__TEST_PERMS:TestPermsAnt\">
+					<featureId>TestPermsAnt.1</featureId>
+				</item>
 			</root>
 		";
 		$xsl = '<?xml version="1.0" encoding="UTF-8"?>';
 		$xsl .= '
 			<xsl:stylesheet version="1.0"
-			     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-			     xmlns:php="http://php.net/xsl">
+				xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+				xmlns:php="http://php.net/xsl">
 			<xsl:output method="html" encoding="utf-8" indent="yes"/>
-			 <xsl:template match="root">
-			  <html><body>
-			    <h2>Items</h2>
-			    <table class="table table-bordered">
+			<xsl:template match="root">
+			<html><body>
+				<h2>Items</h2>
+				<table class="table table-bordered">
 						<thead>
 							<tr>
 								<th>featureId</th>
 								<th>item</th>
 							</tr>
 						</thead>
-			    <xsl:for-each select="item">
-			      <tr>
+				<xsl:for-each select="item">
+				<tr>
 							<td><xsl:value-of select="featureId"/></td>
 							<td><pre>
-				        <xsl:copy-of select="php:function(\'testFetchWfs\', string(@typeName), string(featureId))/node()"/>
-				      </pre></td>
+						<xsl:copy-of select="php:function(\'testFetchWfs\', string(@typeName), string(featureId))/node()"/>
+					</pre></td>
 						</tr>
-			    </xsl:for-each>
-			    </table>
+				</xsl:for-each>
+				</table>
 
 					<h3>Test function to get xml content from php function</h3>
 					<xsl:copy-of select="php:function(\'testHtmlContent\', \'test-arg-1-from-xsl\', \'test-arg-2-from-xsl\')/node()"/>
 
-			  </body></html>
-			 </xsl:template>
+			</body></html>
+			</xsl:template>
 			</xsl:stylesheet>
 		';
 		$xmldoc = new DOMDocument;

+ 563 - 0
SE/se-lib/Route/UrlAction/UserContact.php

@@ -0,0 +1,563 @@
+<?php
+
+Lib::loadClass('RouteBase');
+
+// Tool for Windykacja status table to store contacts with clients
+class Route_UrlAction_UserContact extends RouteBase {
+
+	function defaultAction() { UI::layout([ $this, 'defaultView' ]); }
+	function defaultView() {
+		$idUser = V::get('id_user', 0, $_GET, 'int');
+		if ($idUser <= 0) throw new Exception("Missing id client");
+
+		echo UI::h('h3', [ 'style' => "margin-bottom: 24px; border-bottom: 1px solid #eee" ], "Dodaj notatkę ze spotkania z klientem {$idUser}");
+		$this->viewUserInfo($idUser);
+
+		$postTask = V::get('_postTask', '', $_POST);
+		switch ($postTask) {
+			case 'saveConcat': $this->saveContactPostTask($idUser); break;
+			case '': break;
+			default: throw new Exception("Not implemented postTask '{$postTask}'");
+		}
+		$this->viewSaveContactForm($idUser);
+
+		echo UI::h('hr');
+
+		$this->viewHistContacts($idUser);
+	}
+
+	function viewSaveContactForm($idUser) {
+		$windykInfo = $this->getWindykacjaInfo($idUser);
+		$windykStatusInfo = ($windykInfo) ? $windykInfo['A_STATUS_INFO'] : '';
+		$windykReminder = ($windykInfo && (
+			!empty($windykInfo['L_APPOITMENT_USER'])
+			|| ( !empty($windykInfo['L_APPOITMENT_DATE']) && '0000-00-00' != $windykInfo['L_APPOITMENT_DATE'] )
+			|| !empty($windykInfo['L_APPOITMENT_INFO'])
+		)) ? [
+			'Pracownik' => $windykInfo['L_APPOITMENT_USER'],
+			'Data przypomnienia' => $windykInfo['L_APPOITMENT_DATE'],
+			'Adnotacje' => $windykInfo['L_APPOITMENT_INFO'],
+		] : null;
+		$args = [];
+		$args['L_APPOITMENT_USER'] = V::get('L_APPOITMENT_USER', User::getLogin(), $_POST);
+		$args['L_APPOITMENT_DATE'] = V::get('L_APPOITMENT_DATE', date("Y-m-d H:i"), $_POST);
+		$args['L_APPOITMENT_TYPE'] = V::get('L_APPOITMENT_TYPE', '', $_POST);
+		$args['L_APPOITMENT_INFO'] = V::get('L_APPOITMENT_INFO', '', $_POST);
+		$args['change_windykacja_status'] = V::get('change_windykacja_status', '', $_POST);
+		$args['A_STATUS_INFO'] = V::get('A_STATUS_INFO', $windykStatusInfo, $_POST);
+		$args['add_reminder'] = V::get('add_reminder', '', $_POST);
+		$args['REMINDER_USER'] = V::get('REMINDER_USER', User::getLogin(), $_POST);
+		$args['REMINDER_DATE'] = V::get('REMINDER_DATE', '', $_POST);
+		$args['REMINDER_INFO'] = V::get('REMINDER_INFO', '', $_POST);
+
+		echo UI::h('form', [ 'method' => "POST" ], [
+			UI::h('input', [ 'type' => "hidden", 'name' => '_postTask', 'value' => "saveConcat" ]),
+			UI::h('input', [ 'type' => "hidden", 'name' => 'id_user', 'value' => $idUser ]),
+			UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+				UI::h('div', [ 'class' => "col-md-6" ], [
+					UI::h('label', [], "Pracownik"),
+					UI::h('input', [ 'class' => "form-control", 'type' => "text", 'name' => 'L_APPOITMENT_USER', 'value' => $args['L_APPOITMENT_USER'] ]),
+				]),
+				UI::h('div', [ 'class' => "col-md-3" ], [
+					UI::h('label', [], "Data spotkania"),
+					UI::h('div', [ 'class' => "input-group" ], [
+						UI::h('input', [ 'class' => "form-control se_type-datetime", 'data-format' => "yyyy-MM-dd hh:mm", 'type' => "text", 'name' => 'L_APPOITMENT_DATE', 'value' => $args['L_APPOITMENT_DATE'] ]),
+						UI::h('span', [ 'class' => "input-group-addon" ], [
+							UI::h('span', [ 'class' => "glyphicon glyphicon-calendar" ]),
+						]),
+					]),
+				]),
+				UI::h('div', [ 'class' => "col-md-3" ], [
+					UI::h('label', [], "Rodzaj spotkania"),
+					UI::h('select', [ 'class' => "form-control", 'name' => 'L_APPOITMENT_TYPE' ], [
+						UI::h('option', [ 'value' => "" ]),
+						UI::h('option', array_merge([ 'value' => 'LIVE' ], ('LIVE' === $args['L_APPOITMENT_TYPE']) ? [ 'selected' => "selected" ] : []), "LIVE"),
+						UI::h('option', array_merge([ 'value' => 'TEL' ], ('TEL' === $args['L_APPOITMENT_TYPE']) ? [ 'selected' => "selected" ] : []), "TEL"),
+						UI::h('option', array_merge([ 'value' => 'MAIL' ], ('MAIL' === $args['L_APPOITMENT_TYPE']) ? [ 'selected' => "selected" ] : []), "MAIL"),
+						UI::h('option', array_merge([ 'value' => 'SMS' ], ('SMS' === $args['L_APPOITMENT_TYPE']) ? [ 'selected' => "selected" ] : []), "SMS"),
+						UI::h('option', array_merge([ 'value' => 'INNE' ], ('INNE' === $args['L_APPOITMENT_TYPE']) ? [ 'selected' => "selected" ] : []), "INNE"),
+					]),
+				]),
+			]),
+			UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+				UI::h('div', [ 'class' => "col-md-12" ], [
+					UI::h('label', [], "Notatka"),
+					UI::h('textarea', [ 'class' => "form-control", 'type' => "text", 'name' => 'L_APPOITMENT_INFO' ], $args['L_APPOITMENT_INFO']),
+				]),
+			]),
+			UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+				UI::h('div', [ 'class' => "col-md-12", 'style' => "border:1px solid #eee" ], [
+					UI::h('div', [ 'class' => "checkbox" ], [
+						UI::h('label', [ 'style' => "font-size:14px; line-height:22px" ], [
+							UI::h('input', array_merge([
+								'type' => "checkbox",
+								'name' => "change_windykacja_status",
+								'onClick' => "document.getElementById('fld__change_windykacja_status').style.display = event.target.checked ? 'block' : 'none'",
+							], ($args['change_windykacja_status']) ? [ 'checked' => "checked" ] : [])),
+							" Zmień status windykacji (ustalenia z klientem)",
+						]),
+					]),
+					UI::h('div', [
+						'id' => "fld__change_windykacja_status",
+						'style' => "display:" . ($args['change_windykacja_status'] ? 'block' : 'none') . "; padding-bottom:12px",
+					], [
+						// UI::h('label', [], "Status windykacji:"),
+						UI::h('p', [], [
+							"Obecny status: ",
+							($windykStatusInfo) ? $windykStatusInfo : "<em>brak</em>",	
+						]),
+						UI::h('input', [ 'class' => "form-control", 'type' => "text", 'name' => 'A_STATUS_INFO', 'value' => $args['A_STATUS_INFO'] ]),
+					]),
+				]),
+			]),
+			UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+				UI::h('div', [ 'class' => "col-md-12", 'style' => "border:1px solid #eee" ], [
+					UI::h('div', [ 'class' => "checkbox" ], [
+						UI::h('label', [ 'style' => "font-size:14px; line-height:22px" ], [
+							UI::h('input', array_merge([
+								'type' => "checkbox",
+								'name' => "add_reminder",
+								'onClick' => "document.getElementById('fld__add_reminder').style.display = event.target.checked ? 'block' : 'none'",
+							], ($args['add_reminder']) ? [ 'checked' => "checked" ] : [])),
+							" Przypomnienie",
+						]),
+					]),
+					UI::h('div', [
+						'id' => "fld__add_reminder",
+						'style' => "display:" . ($args['add_reminder'] ? 'block' : 'none') . "; padding-bottom:4px",
+					], [
+						($windykReminder) ? UI::h('div', [ 'style' => "border-left: 5px solid orange; padding:6px 12px; margin-bottom:12px" ], [
+							UI::h('p', [], "Obecne przypomnienie (Uwaga: zostanie nadpisane)"),
+							UI::hTable([ 'rows' => [ $windykReminder ], 'disable_lp' => true, 'style' => "margin-bottom:0" ]),
+						]) : '',
+						UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+							UI::h('div', [ 'class' => "col-md-6" ], [
+								UI::h('label', [], "Pracownik"),
+								UI::h('input', [ 'class' => "form-control", 'type' => "text", 'name' => 'REMINDER_USER', 'value' => $args['REMINDER_USER'] ]),
+							]),
+							UI::h('div', [ 'class' => "col-md-3" ], [
+								UI::h('label', [], "Data przypomnienia"),
+								UI::h('div', [ 'class' => "input-group" ], [
+									UI::h('input', [ 'class' => "form-control se_type-datetime", 'data-format' => "yyyy-MM-dd hh:mm", 'type' => "text", 'name' => 'REMINDER_DATE', 'value' => $args['REMINDER_DATE'] ]),
+									UI::h('span', [ 'class' => "input-group-addon" ], [
+										UI::h('span', [ 'class' => "glyphicon glyphicon-calendar" ]),
+									]),
+								]),
+							]),
+						]),
+						UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+							UI::h('div', [ 'class' => "col-md-9" ], [
+								UI::h('label', [], "Adnotacje"),
+								UI::h('textarea', [ 'class' => "form-control", 'type' => "text", 'name' => 'REMINDER_INFO' ], $args['REMINDER_INFO']),
+							]),
+						]),
+					]),
+				]),
+			]),
+			// <div class="form-group">
+			// 	<div class="col-sm-offset-2 col-sm-10">
+			// 	<div class="checkbox">
+			// 		<label>
+			// 		<input type="checkbox"> Remember me
+			// 		</label>
+			// 	</div>
+			// 	</div>
+			// </div>
+			UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+				UI::h('div', [ 'class' => "col-md-12" ], [
+					UI::h('button', [ 'class' => "btn btn-primary" ], "Zapisz notatkę"),
+				]),
+			]),
+		]);
+	}
+	function saveContactPostTask($idUser) {
+		$args = [];
+		$args['L_APPOITMENT_USER'] = V::get('L_APPOITMENT_USER', '', $_POST);
+		$args['L_APPOITMENT_DATE'] = V::get('L_APPOITMENT_DATE', '', $_POST);
+		$args['L_APPOITMENT_TYPE'] = V::get('L_APPOITMENT_TYPE', '', $_POST);
+		$args['L_APPOITMENT_INFO'] = V::get('L_APPOITMENT_INFO', '', $_POST);
+		$args['change_windykacja_status'] = V::get('change_windykacja_status', '', $_POST);
+		$args['A_STATUS_INFO'] = V::get('A_STATUS_INFO', '', $_POST);
+		$args['add_reminder'] = V::get('add_reminder', '', $_POST);
+		$args['REMINDER_USER'] = V::get('REMINDER_USER', '', $_POST);
+		$args['REMINDER_DATE'] = V::get('REMINDER_DATE', '', $_POST);
+		$args['REMINDER_INFO'] = V::get('REMINDER_INFO', '', $_POST);
+
+		try {
+			if (empty($args['L_APPOITMENT_USER'])) throw new Exception("Brak pracownika");
+			if (empty($args['L_APPOITMENT_DATE'])) throw new Exception("Brak daty spotkania");
+			if (empty($args['L_APPOITMENT_TYPE'])) throw new Exception("Brak rodzaju spotkania");
+			if (empty($args['L_APPOITMENT_INFO'])) throw new Exception("Brak treści notatki");
+			if ($args['change_windykacja_status']) {
+				if (empty($args['A_STATUS_INFO'])) throw new Exception("Brak statusu windykacji");
+			}
+			if ($args['add_reminder']) {
+				if (empty($args['REMINDER_USER'])) throw new Exception("Brak pracownika w przypomnieniu");
+				if (empty($args['REMINDER_DATE'])) throw new Exception("Brak daty przypomnienia");
+				if (empty($args['REMINDER_INFO'])) throw new Exception("Brak adnotacji przypomnienia");
+			}
+
+			$windykInfo = $this->getWindykacjaInfo($idUser);
+
+			// create new record with L_APPOITMENT_* in table `USERS2_CONTACT`
+			DB::getPDO()->insert('USERS2_CONTACT', [
+				'ID_BILLING_USERS' => $idUser,
+				'L_APPOITMENT_USER' => $args['L_APPOITMENT_USER'],
+				'L_APPOITMENT_DATE' => $args['L_APPOITMENT_DATE'],
+				'L_APPOITMENT_TYPE' => $args['L_APPOITMENT_TYPE'],
+				'L_APPOITMENT_INFO' => $args['L_APPOITMENT_INFO'],
+				'SALDO' => $windykInfo['SALDO'],
+				'A_RECORD_CREATE_DATE' => "NOW()",
+				'A_RECORD_CREATE_AUTHOR' => User::getLogin(),
+			]);
+			// update A_STATUS_INFO in `USERS2_WINDYKACJA_STATUS` if $args['change_windykacja_status']
+			if ($args['change_windykacja_status']) {
+				DB::getPDO()->update('USERS2_WINDYKACJA_STATUS', 'ID_BILLING_USERS', $idUser, [
+					'A_STATUS_INFO' => $args['A_STATUS_INFO'],
+					'A_RECORD_UPDATE_DATE' => "NOW()",
+					'A_RECORD_UPDATE_AUTHOR' => User::getLogin(),
+				]);
+			}
+			// update L_APPOITMENT_* fields using REMINDER_* in `USERS2_WINDYKACJA_STATUS` if $args['add_reminder']
+			if ($args['add_reminder']) {
+				DB::getPDO()->update('USERS2_WINDYKACJA_STATUS', 'ID_BILLING_USERS', $idUser, [
+					'L_APPOITMENT_USER' => $args['REMINDER_USER'],
+					'L_APPOITMENT_DATE' => $args['REMINDER_DATE'],
+					'L_APPOITMENT_INFO' => $args['REMINDER_INFO'],
+					'A_RECORD_UPDATE_DATE' => "NOW()",
+					'A_RECORD_UPDATE_AUTHOR' => User::getLogin(),
+				]);
+			}
+		} catch (Exception $e) {
+			DBG::log($e);
+			UI::alert('danger', $e->getMessage());
+			return;
+		}
+
+		throw new AlertSuccessException("Wprowadzono notatkę ze spotkania z klientem " . UI::h('a', [ 'href' => $this->getLink('', [ 'id_user' => $idUser ]) ], "wróć"));
+	}
+
+	function viewUserInfo($idUser) {
+		$userInfo = $this->getUserInfo($idUser);
+		$windykInfo = $this->getWindykacjaInfo($idUser);
+
+		echo UI::h('details', [ 'style' => "margin-bottom: 24px" ], [
+			UI::h('summary', [ 'style' => "padding: 12px; background: #eee; cursor: pointer; outline: none" ], [
+				($userInfo['is_firma']) ? "Firma" : "Klient",
+				" ",
+				UI::h('b', [], "{$userInfo['P_NAME']} {$userInfo['P_NAME_SECOND']}"),
+				$windykInfo ? UI::h('span', [ 'style' => "padding-left: 24px" ], [
+					"Zapis/Odczyt dla: {$windykInfo['A_ADM_COMPANY']}", // $windykInfo['A_CLASSIFIED'];
+				]) : "",
+				UI::h('span', [ 'style' => "padding-left: 24px" ], [
+					"Status windykacji: ",
+					(!$windykInfo) ? "brak danych" : UI::h('span', [], "{$windykInfo['A_STATUS']} <em>{$windykInfo['A_STATUS_INFO']}</em>"),
+				]),
+				" ...",
+				UI::h('span', [ 'style' => "float:right" ], [
+					UI::h('a', [ 'href' => "index.php?MENU_INIT=USERS2_WINDYKACJA_STATUS&q=&_f=&_user_id={$idUser}" ], "Panel windykacji"),
+				]),
+			]),
+			UI::h('div', [ 'style' => "padding: 12px; background: #eee;" ], [
+				UI::h('table', [], [
+					UI::h('tr', [], [
+						UI::h('td', [ 'style' => "vertical-align:top; padding-right:8px" ], [
+							$this->viewWidgetUserInfo($idUser, $userInfo),
+						]),
+						UI::h('td', [ 'style' => "vertical-align:top; padding-right:8px" ], [
+							$this->viewWidgetUserWindykInfo($idUser, $windykInfo),
+						]),
+					]),
+				]),
+			]),
+		]);
+	}
+	function viewWidgetUserInfo($idUser, $userInfo = []) {
+		if (empty($userInfo)) $userInfo = $this->getUserInfo($idUser);
+
+		$outUserInfo = [
+			'Nr klienta' => $userInfo['ID_BILLING_USERS'],
+			'Firma' => ($userInfo['is_firma']) ? "Tak" : "Nie",
+			'Nip' => $userInfo['P_NIP'],
+			'Pesel' => $userInfo['P_PESEL'],
+			'Tel' => $userInfo['P_PHONE'],
+			'email' => $userInfo['user_mail_contact'],
+			'Adres' => "{$userInfo['P_ADDRESS_POST_CODE']} {$userInfo['P_ADDRESS_CITY']} {$userInfo['P_ADDRESS_STREET']} {$userInfo['P_ADDRESS_HOME']}/{$userInfo['P_ADDRESS_HOUSE']}",
+			'Operator' => "[{$userInfo['BILLING_OWNER']}] {$userInfo['BILLING_OWNER_NAME']}",
+		];
+
+		return UI::h('div', [], [
+			UI::h('p', [ 'style' => "border-left:3px solid #ddd; padding-left:6px; font-weight:bold" ], "Dane klienta"),
+			UI::h('table', [ 'class' => "table table-bordered", 'style' => "width:auto; background:#fff" ],
+				array_map(function ($fieldName) use ($outUserInfo) {
+				return UI::h('tr', [], [
+					UI::h('th', [], $fieldName),
+					UI::h('td', [], $outUserInfo[$fieldName]),
+				]);
+				}, array_keys($outUserInfo))
+			),
+		]);
+	}
+	function viewWidgetUserWindykInfo($idUser, $windykInfo = []) {
+		if (empty($windykInfo)) $windykInfo = $this->getUserInfo($idUser);
+
+		$outWindykInfo = [
+			'Status' => $windykInfo['A_STATUS'],
+
+			// $filter_arr['stan_zero'] = array("stan zerowy");
+			// $filter_arr['10-ego'] = array("termin płatności faktur, saldo na -", 'desc'=>array("10-ego", "10-ego każdego miesiąca - termin płatnosci faktur oraz saldo na minusie"));
+			// $filter_arr['WAITING'] = array("termin płatności faktur", 'desc'=>array("10-ego", "10-ego każdego miesiąca - termin płatnosci faktur"));// z regulaminu
+			// $filter_arr['15'] = array("mail ponaglenie", 'desc'=>array("10-ego + 5", "10-ego + 5 dni roboczych"));
+			// $filter_arr['blokada'] = array("blokada", 'desc'=>array("pn, wt, śr około 25", "najbliższy pn, wt, śr do 25"));// najbliższy pn, wt, śr do 25-ego danego miesiąca
+			// $filter_arr['tel1'] = array("kontakt tel.", 'desc'=>array("", "Ostatni kontakt tel. ponad 3 m-ce temu"));
+			// $filter_arr['bad_address'] = array("błędny adres");
+			// $filter_arr['wezwanie1'] = array("wezwanie do zapłaty", 'desc'=>array("2 x FVat", "2 faktury"));
+			// $filter_arr['waiting-wezwanie2'] = array("oczekiwanie 7 dni od terminu płatności");
+			// $filter_arr['wezwanie2'] = array("ostateczne wezwanie do zapłaty, rozwiązanie umowy", 'desc'=>array("wezwanie + 14", "wezwanie + 14 dni"));
+			// $filter_arr['waiting-krd'] = array("min. 30 dni od ostatecznego wezwania");
+			// $filter_arr['krd'] = array("do przekazania do KRD", 'desc'=>array("wezwanie ost. + 60", "wezwanie ost. + 60 dni"));
+			// $filter_arr['waiting-sad'] = array("przekazano do KRD");
+			// $filter_arr['docs-in-sad'] = array("przekazać sprawę do sądu");// waiting-sad = 30 dni
+			// $filter_arr['sad'] = array("przekazano sprawę do sądu");
+
+			// $filter_arr['3 m-ce przed'] = array("3 m-ce przed przedawnieniem");// 3 m-ce przed przedawnieniem
+			// $filter_arr['po-terminie'] = array("po terminie");// 3 lata po dacie platnosci faktur
+			// $filter_arr['has_nr_sad'] = array("nr sprawy sąd");
+			// $filter_arr['has_nr_komornik'] = array("nr sprawy komornik");
+			// $filter_arr['has_ustalenia'] = array("ustalenia z klientem");
+			// $filter_arr['sad_and_komornik'] = array("sąd z komornik");
+			// $filter_arr['sad_bez_komornik'] = array("sąd bez komornik");
+			// $filter_arr['isMovedToVectra'] = array("przeniesieni do Vectra");
+
+			// if ($filter_selected == 'po-terminie') {
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`!='0000-00-00'";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`<DATE_SUB(NOW(), INTERVAL 36 MONTH)";
+			// }
+			// else if ($filter_selected == '3 m-ce przed') {
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`!='0000-00-00'";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`<DATE_SUB(NOW(), INTERVAL 33 MONTH)";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`>DATE_SUB(NOW(), INTERVAL 36 MONTH)";
+			// }
+			// else if ($filter_selected == 'tel1') {
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// 	$sql_where_and_arr[] = "w.`A_STATUS` in('wezwanie1', 'waiting-wezwanie2', 'wezwanie2', 'waiting-krd')";
+			// 	$sql_where_and_arr[] = "( w.`LAST_PHONE_STATUS_DATE`='0000-00-00'
+			// 		or w.`LAST_PHONE_STATUS_DATE`<DATE_SUB(NOW(), INTERVAL 3 MONTH) )";
+			// }
+			// else if ($filter_selected == 'bad_address') {
+			// 	$sql_where_and_arr[] = "w.`BAD_ADDRESS`>0";
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// }
+			// else if ($filter_selected == 'stan_zero') {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='WAITING'";
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`>-0.05";
+			// }
+			// else if ($filter_selected == '10-ego') {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='WAITING'";
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// }
+			// else if ($filter_selected == 'has_nr_sad') {
+			// 	$sql_where_and_arr[] = "w.`NR_SPRAWY_SAD`!=''";
+			// }
+			// else if ($filter_selected == 'has_nr_komornik') {
+			// 	$sql_where_and_arr[] = "w.`NR_SPRAWY_KOMORNIK`!=''";
+			// }
+			// else if ($filter_selected == 'has_ustalenia') {
+			// 	$sql_where_and_arr[] = "w.`L_APPOITMENT_INFO`!=''";
+			// }
+			// else if ($filter_selected == 'sad_and_komornik') {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='sad' and w.`NR_SPRAWY_KOMORNIK`!=''";
+			// }
+			// else if ($filter_selected == 'sad_bez_komornik') {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='sad' and w.`NR_SPRAWY_KOMORNIK`=''";
+			// }
+			// else if ($filter_selected == 'isMovedToVectra') {
+			// 	$sql_where_and_arr[] = "w.`IS_MOVED_TO_VECTRA`=1";
+			// }
+			// else {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='{$filter_selected}'";
+			// }
+
+			'Saldo' => $windykInfo['PAY_SALDO'],
+			'Termin ost. fv' => $windykInfo['PAY_DATE'], // Data wymagalności ostatniej faktury
+			'Zaległe faktury' => $windykInfo['PAY_FVAT'],
+			'Adresy usług' => $windykInfo['SERVICES_STREETS'],
+		];
+
+		return UI::h('div', [], [
+			UI::h('p', [ 'style' => "border-left:3px solid #ddd; padding-left:6px; font-weight:bold" ], [
+				"Informacje o płatnościach",
+				($windykInfo['A_STATUS_UPDATE_DATE'] < date("Y-m-d"))
+				?	UI::h('div', [ 'class' => "text-danger" ], [
+						"Uwaga: Wymagają aktualizacji",
+						" ",
+						UI::h('em', [ 'class' => "text-muted" ], "(dane z {$windykInfo['A_STATUS_UPDATE_DATE']})"),
+						// TODO: btn aktualizuj lub auto aktualizuj przed pokazaniem danych
+					])
+				:	"",
+			]),
+			UI::h('table', [ 'class' => "table table-bordered", 'style' => "width:auto; background:#fff" ],
+				array_map(function ($fieldName) use ($outWindykInfo) {
+				return UI::h('tr', [], [
+					UI::h('th', [], $fieldName),
+					UI::h('td', [], $outWindykInfo[$fieldName]),
+				]);
+				}, array_keys($outWindykInfo))
+			),
+			UI::h('table', [ 'class' => "table table-bordered", 'style' => "width:auto; display:none; background:yellow" ],
+				array_map(function ($fieldName) use ($windykInfo) {
+				return UI::h('tr', [], [
+					UI::h('th', [], $fieldName),
+					UI::h('td', [], $windykInfo[$fieldName]),
+				]);
+				}, array_keys($windykInfo))
+			),
+		]);
+	}
+
+	function viewHistContacts($idUser) {
+		$lastHist = DB::getPDO()->fetchAll("
+			select t.ID
+				, t.ID_BILLING_USERS
+		--		, t.CLIENT_INFO
+				, t.A_STATUS
+		--		, t.A_STATUS_INFO
+				, t.L_APPOITMENT_USER
+				, t.L_APPOITMENT_DATE
+				, t.L_APPOITMENT_TYPE
+				, t.L_APPOITMENT_INFO
+			from USERS2_CONTACT t
+			where t.ID_BILLING_USERS = :id
+			limit 11
+		", [ ':id' => $idUser ]);
+
+		UI::table([ 'caption' => "Historia kontaktów z klientem (TODO: w trakcie prac. Zobacz " .
+			UI::h('a', [ 'href' => "index.php?MENU_INIT=USERS2_WINDYKACJA_STATUS&q=&_f=&_user_id={$idUser}" ], "Panel windykacji") .
+		")", 'rows' => $lastHist ]);
+		// if (count($lastHist) > 10) UI::alert('info', "Przeglądaj wszystkie wpisy TODO: link to view table with ff_ID_BILLING_USERS");
+	}
+
+	function getUserInfo($idUser) {
+		return DB::getPDO()->fetchFirst("
+			select bua.`id_users`
+				, bua.`id_users` as ID_BILLING_USERS
+				, bua.`is_firma`
+				, bua.`P_NAME`
+				, bua.`P_NAME_SECOND`
+				, bua.`P_NIP`
+				, bua.`P_PESEL`
+				, bua.`P_PHONE`
+				, bua.`P_ADDRESS_REGION`
+				, bua.`P_ADDRESS_CITY`
+				, bua.`P_ADDRESS_POST_CODE`
+				, bua.`P_ADDRESS_STREET`
+				, bua.`P_ADDRESS_HOME`
+				, bua.`P_ADDRESS_HOUSE`
+				, bua.`user_mail_contact`
+				, bu.`BILLING_OWNER`
+				, ( select bo.name1 from BILLING_OWNER bo where bo.ID = bu.BILLING_OWNER ) as BILLING_OWNER_NAME
+			from `BILLING_USERS_ADD` as bua
+				left join `BILLING_USERS` as bu on ( bu.`ID` = bua.`id_users` )
+			where bua.`id_users` = :id_user
+		", [ ':id_user' => $idUser ]);
+	}
+	function getWindykacjaInfo($idUser) {
+		if ($this->_windykInfo) return $this->_windykInfo;
+
+		$this->_windykInfo = $this->fetchWindykacjaInfo($idUser);
+		if ($this->_windykInfo['A_STATUS_UPDATE_DATE'] < date("Y-m-d")) {
+			Lib::loadClass('Windykacja_StatsModel');
+			Lib::loadClass('Windykacja_StatsHelper');
+			$user = Windykacja_StatsModel::get_user_by_id($idUser);
+			if ($user) {
+				$billing_docs = Windykacja_StatsModel::get_bill_dosc_by_date($user);
+				Windykacja_StatsHelper::update_stats($user, $billing_docs);
+			}
+			$this->_windykInfo = $this->fetchWindykacjaInfo($idUser);
+		}
+		return $this->_windykInfo;
+	}
+	function fetchWindykacjaInfo($idUser) {
+		return DB::getPDO()->fetchFirst("
+			select t.`ID`
+				, t.A_STATUS
+				, t.A_STATUS_INFO
+				, t.*
+			from `USERS2_WINDYKACJA_STATUS` as t
+			where t.`ID_BILLING_USERS` = :id_user
+		", [ ':id_user' => $idUser ]);
+	}
+
+	function prepareTable() {
+		DB::getPDO()->execSql("
+			CREATE TABLE IF NOT EXISTS `USERS2_CONTACT` (
+				`ID` int(11) NOT NULL AUTO_INCREMENT,
+				`ID_BILLING_USERS` int(11) NOT NULL DEFAULT '0',
+				`CLIENT_INFO` varchar(255) NOT NULL DEFAULT '',
+				`A_STATUS` enum('WAITING','NORMAL','DELETED') NOT NULL DEFAULT 'WAITING',
+				`A_STATUS_INFO` varchar(255) NOT NULL,
+				`L_APPOITMENT_DATE` date NOT NULL DEFAULT '0000-00-00',
+				`L_APPOITMENT_USER` varchar(20) NOT NULL DEFAULT '',
+				`L_APPOITMENT_PERIOD` varchar(4) NOT NULL,
+				`L_APPOITMENT_INFO` varchar(255) NOT NULL,
+				`L_APPOITMENT_TYPE` enum('','LIVE','TEL','MAIL','SMS','INNE') DEFAULT NULL,
+				`A_RECORD_CREATE_DATE` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+				`A_RECORD_CREATE_AUTHOR` varchar(20) NOT NULL DEFAULT '',
+				`A_RECORD_UPDATE_DATE` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+				`A_RECORD_UPDATE_AUTHOR` varchar(20) NOT NULL DEFAULT '',
+				`A_PROBLEM` enum('','WARNING','PROBLEM','SERIOUS','UNVERIFIED') DEFAULT NULL,
+				`A_PROBLEM_DESC` varchar(255) NOT NULL DEFAULT '',
+				`A_PROBLEM_DATE` varchar(30) NOT NULL DEFAULT '',
+				`T_WORKPOINTS` varchar(100) NOT NULL DEFAULT '',
+				`T_WORKPOINTS_VALUE` varchar(100) NOT NULL DEFAULT '',
+				`T_WORKPOINTS_TYPE` varchar(100) NOT NULL DEFAULT '',
+				`T_WORKPOINTS_USER` varchar(100) NOT NULL DEFAULT '',
+				`T_WORKPOINTS_DATE` varchar(100) NOT NULL DEFAULT '',
+				`A_CLASSIFIED` varchar(100) NOT NULL DEFAULT '',
+				`A_ADM_COMPANY` varchar(100) NOT NULL DEFAULT '',
+				`ROZLICZ_MONTH` date NOT NULL DEFAULT '0000-00-00',
+				`ROZLICZ_CONFIRM` int(11) NOT NULL DEFAULT '0',
+				`SALDO` decimal(10,2) NOT NULL DEFAULT '0.00',
+				`ILE_ODZYSKANO` decimal(10,2) NOT NULL DEFAULT '0.00',
+				PRIMARY KEY (`ID`)
+			) ENGINE=MyISAM DEFAULT CHARSET=latin2;
+		");
+		DB::getPDO()->execSql("
+			CREATE TABLE IF NOT EXISTS `USERS2_CONTACT_HIST` (
+				`ID` int(11) NOT NULL AUTO_INCREMENT,
+				`ID_USERS2` int(11) NOT NULL,
+				`ID_BILLING_USERS` varchar(11) NOT NULL DEFAULT 'N/S;',
+				`CLIENT_INFO` varchar(255) NOT NULL DEFAULT 'N/S;',
+				`A_STATUS` varchar(32) NOT NULL DEFAULT 'N/S;',
+				`A_STATUS_INFO` varchar(255) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_DATE` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_USER` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_PERIOD` varchar(4) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_INFO` varchar(255) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_TYPE` varchar(16) NOT NULL DEFAULT 'N/S;',
+				`A_RECORD_CREATE_DATE` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`A_RECORD_CREATE_AUTHOR` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`A_RECORD_UPDATE_DATE` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`A_RECORD_UPDATE_AUTHOR` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`A_PROBLEM` varchar(16) NOT NULL DEFAULT 'N/S;',
+				`A_PROBLEM_DESC` varchar(255) NOT NULL DEFAULT 'N/S;',
+				`A_PROBLEM_DATE` varchar(30) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS_VALUE` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS_TYPE` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS_USER` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS_DATE` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`A_CLASSIFIED` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`A_ADM_COMPANY` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`ROZLICZ_MONTH` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`ROZLICZ_CONFIRM` varchar(11) NOT NULL DEFAULT 'N/S;',
+				`SALDO` varchar(11) NOT NULL DEFAULT 'N/S;',
+				`ILE_ODZYSKANO` varchar(11) NOT NULL DEFAULT 'N/S;',
+				PRIMARY KEY (`ID`),
+				KEY `ID_USERS2` (`ID_USERS2`)
+			) ENGINE=MyISAM DEFAULT CHARSET=latin2;
+		");
+	}
+}

+ 483 - 0
SE/se-lib/Route/UrlAction/UserSaldoAfterContact.php

@@ -0,0 +1,483 @@
+<?php
+
+Lib::loadClass('RouteBase');
+
+// Windykacja - oblicz ile odzyskano po kontakcie
+class Route_UrlAction_UserSaldoAfterContact extends RouteBase {
+
+	function defaultAction() { UI::layout([ $this, 'defaultView' ]); }
+	function defaultView() {
+		$id = V::get('ID', 0, $_GET, 'int');
+		if ($id <= 0) throw new Exception("Missing id");
+
+		echo UI::h('h3', [ 'style' => "margin-bottom: 24px; border-bottom: 1px solid #eee" ], "Sprawdź ile idzyskano po kontakcie z klientem");
+		$contactInfo = $this->getContactInfo($id);
+		if (!$contactInfo) throw new Exception("Rekord o podanym numerze nie istnieje");
+
+		$idUser = $contactInfo['ID_BILLING_USERS'];
+		if ($idUser <= 0) throw new Exception("Brak numeru klienta");
+		$this->viewUserInfo($idUser);
+
+		$postTask = V::get('_postTask', '', $_POST);
+		switch ($postTask) {
+			case 'saveIleOdzyskano': $this->saveIleOdzyskanoPostTask($idUser); break;
+			case '': break;
+			default: throw new Exception("Not implemented postTask '{$postTask}'");
+		}
+		$this->viewIleOdzyskanoForm($id);
+
+		echo UI::h('hr');
+
+		$this->viewHistContacts($idUser);
+	}
+
+	function viewIleOdzyskanoForm($id) {
+		$contactInfo = $this->getContactInfo($id);
+		$idUser = $contactInfo['ID_BILLING_USERS'];
+		$listContactAfter = DB::getPDO()->fetchAll("
+			select *
+			from USERS2_CONTACT
+			where ID_BILLING_USERS = :id_user
+				and A_RECORD_CREATE_DATE > :contact_date
+				and A_STATUS != 'DELETED'
+		", [ ':id_user' => $idUser, ':contact_date' => $contactInfo['A_RECORD_CREATE_DATE'] ]);
+		$lastContactId = (!empty($listContactAfter)) ? end($listContactAfter)['ID'] : null;
+
+		echo UI::h('form', [ 'method' => "POST", 'class' => "form-inline" ], [
+			UI::h('input', [ 'type' => "hidden", 'name' => '_postTask', 'value' => "saveIleOdzyskano" ]),
+			UI::h('input', [ 'type' => "hidden", 'name' => 'id', 'value' => $id ]),
+			UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+				"Notatka ze spotkania:",
+				UI::hTable([ 'rows' => [ $contactInfo ] ]),
+			]),
+			($listContactAfter)
+			?	UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+					UI::h('div', [ 'class' => "alert alert-warning" ], [
+						"Uwaga: Znaleziono późniejsze notatki z klientem",
+						" ",
+						UI::h('a', [ 'href' => $this->getLink('', [ 'ID' => $lastContactId ]) ], "przejdź do najnowszego kontaktu"),
+					]),
+					UI::hTable([
+						'rows' => array_map(function ($item) {
+							return arraY_merge([
+								'ID' => $item['ID'],
+							], $item);
+						}, $listContactAfter)
+					]),
+				])
+			:	UI::h('div', [], [
+					UI::h('div', [ 'class' => "row", 'style' => "padding-bottom: 12px" ], [
+						UI::h('div', [ 'class' => "col-md-12" ], [
+							UI::h('iframe', [
+								'src' => "https://biuro.biall-net.pl/dev-pl/se-master/index.php?MENU_INIT=USERS2_WINDYKACJA_STATUS&q=&_f=&_user_id={$idUser}",
+								'style' => "width:100%; height:400px; padding:12px; border:1px solid #ddd; border-radius:8px",
+							]),
+						]),
+						UI::h('div', [ 'class' => "col-md-12" ], [
+							UI::h('label', [], "Ile odzyskano:"),
+							" ",
+							UI::h('input', [ 'class' => "form-control", 'type' => "number", 'min' => "0.00", 'step' => "0.01", 'name' => 'ILE_ODZYSKANO', 'value' => $args['ILE_ODZYSKANO'] ]),
+							" ",
+							UI::h('button', [ 'class' => "btn btn-primary" ], "Zapisz"),
+						]),
+					]),
+				])
+			,
+		]);
+	}
+	function saveContactPostTask($idUser) {
+		$args = [];
+		$args['L_APPOITMENT_USER'] = V::get('L_APPOITMENT_USER', '', $_POST);
+		$args['L_APPOITMENT_DATE'] = V::get('L_APPOITMENT_DATE', '', $_POST);
+		$args['L_APPOITMENT_TYPE'] = V::get('L_APPOITMENT_TYPE', '', $_POST);
+		$args['L_APPOITMENT_INFO'] = V::get('L_APPOITMENT_INFO', '', $_POST);
+		$args['change_windykacja_status'] = V::get('change_windykacja_status', '', $_POST);
+		$args['A_STATUS_INFO'] = V::get('A_STATUS_INFO', '', $_POST);
+		$args['add_reminder'] = V::get('add_reminder', '', $_POST);
+		$args['REMINDER_USER'] = V::get('REMINDER_USER', '', $_POST);
+		$args['REMINDER_DATE'] = V::get('REMINDER_DATE', '', $_POST);
+		$args['REMINDER_INFO'] = V::get('REMINDER_INFO', '', $_POST);
+
+		try {
+			if (empty($args['L_APPOITMENT_USER'])) throw new Exception("Brak pracownika");
+			if (empty($args['L_APPOITMENT_DATE'])) throw new Exception("Brak daty spotkania");
+			if (empty($args['L_APPOITMENT_TYPE'])) throw new Exception("Brak rodzaju spotkania");
+			if (empty($args['L_APPOITMENT_INFO'])) throw new Exception("Brak treści notatki");
+			if ($args['change_windykacja_status']) {
+				if (empty($args['A_STATUS_INFO'])) throw new Exception("Brak statusu windykacji");
+			}
+			if ($args['add_reminder']) {
+				if (empty($args['REMINDER_USER'])) throw new Exception("Brak pracownika w przypomnieniu");
+				if (empty($args['REMINDER_DATE'])) throw new Exception("Brak daty przypomnienia");
+				if (empty($args['REMINDER_INFO'])) throw new Exception("Brak adnotacji przypomnienia");
+			}
+
+			$windykInfo = $this->getWindykacjaInfo($idUser);
+
+			// create new record with L_APPOITMENT_* in table `USERS2_CONTACT`
+			DB::getPDO()->insert('USERS2_CONTACT', [
+				'ID_BILLING_USERS' => $idUser,
+				'L_APPOITMENT_USER' => $args['L_APPOITMENT_USER'],
+				'L_APPOITMENT_DATE' => $args['L_APPOITMENT_DATE'],
+				'L_APPOITMENT_TYPE' => $args['L_APPOITMENT_TYPE'],
+				'L_APPOITMENT_INFO' => $args['L_APPOITMENT_INFO'],
+				'SALDO' => $windykInfo['SALDO'],
+				'A_RECORD_CREATE_DATE' => "NOW()",
+				'A_RECORD_CREATE_AUTHOR' => User::getLogin(),
+			]);
+			// update A_STATUS_INFO in `USERS2_WINDYKACJA_STATUS` if $args['change_windykacja_status']
+			if ($args['change_windykacja_status']) {
+				DB::getPDO()->update('USERS2_WINDYKACJA_STATUS', 'ID_BILLING_USERS', $idUser, [
+					'A_STATUS_INFO' => $args['A_STATUS_INFO'],
+					'A_RECORD_UPDATE_DATE' => "NOW()",
+					'A_RECORD_UPDATE_AUTHOR' => User::getLogin(),
+				]);
+			}
+			// update L_APPOITMENT_* fields using REMINDER_* in `USERS2_WINDYKACJA_STATUS` if $args['add_reminder']
+			if ($args['add_reminder']) {
+				DB::getPDO()->update('USERS2_WINDYKACJA_STATUS', 'ID_BILLING_USERS', $idUser, [
+					'L_APPOITMENT_USER' => $args['REMINDER_USER'],
+					'L_APPOITMENT_DATE' => $args['REMINDER_DATE'],
+					'L_APPOITMENT_INFO' => $args['REMINDER_INFO'],
+					'A_RECORD_UPDATE_DATE' => "NOW()",
+					'A_RECORD_UPDATE_AUTHOR' => User::getLogin(),
+				]);
+			}
+		} catch (Exception $e) {
+			DBG::log($e);
+			UI::alert('danger', $e->getMessage());
+			return;
+		}
+
+		throw new AlertSuccessException("Wprowadzono notatkę ze spotkania z klientem " . UI::h('a', [ 'href' => $this->getLink('', [ 'id_user' => $idUser ]) ], "wróć"));
+	}
+
+	function viewUserInfo($idUser) {
+		$userInfo = $this->getUserInfo($idUser);
+		$windykInfo = $this->getWindykacjaInfo($idUser);
+
+		echo UI::h('details', [ 'style' => "margin-bottom: 24px" ], [
+			UI::h('summary', [ 'style' => "padding: 12px; background: #eee; cursor: pointer; outline: none" ], [
+				($userInfo['is_firma']) ? "Firma" : "Klient",
+				" ",
+				UI::h('b', [], "{$userInfo['P_NAME']} {$userInfo['P_NAME_SECOND']}"),
+				$windykInfo ? UI::h('span', [ 'style' => "padding-left: 24px" ], [
+					"Zapis/Odczyt dla: {$windykInfo['A_ADM_COMPANY']}", // $windykInfo['A_CLASSIFIED'];
+				]) : "",
+				UI::h('span', [ 'style' => "padding-left: 24px" ], [
+					"Status windykacji: ",
+					(!$windykInfo) ? "brak danych" : UI::h('span', [], "{$windykInfo['A_STATUS']} <em>{$windykInfo['A_STATUS_INFO']}</em>"),
+				]),
+				" ...",
+				UI::h('span', [ 'style' => "float:right" ], [
+					UI::h('a', [ 'href' => "index.php?MENU_INIT=USERS2_WINDYKACJA_STATUS&q=&_f=&_user_id={$idUser}" ], "Panel windykacji"),
+				]),
+			]),
+			UI::h('div', [ 'style' => "padding: 12px; background: #eee;" ], [
+				UI::h('table', [], [
+					UI::h('tr', [], [
+						UI::h('td', [ 'style' => "vertical-align:top; padding-right:8px" ], [
+							$this->viewWidgetUserInfo($idUser, $userInfo),
+						]),
+						UI::h('td', [ 'style' => "vertical-align:top; padding-right:8px" ], [
+							$this->viewWidgetUserWindykInfo($idUser, $windykInfo),
+						]),
+					]),
+				]),
+			]),
+		]);
+	}
+	function viewWidgetUserInfo($idUser, $userInfo = []) {
+		if (empty($userInfo)) $userInfo = $this->getUserInfo($idUser);
+
+		$outUserInfo = [
+			'Nr klienta' => $userInfo['ID_BILLING_USERS'],
+			'Firma' => ($userInfo['is_firma']) ? "Tak" : "Nie",
+			'Nip' => $userInfo['P_NIP'],
+			'Pesel' => $userInfo['P_PESEL'],
+			'Tel' => $userInfo['P_PHONE'],
+			'email' => $userInfo['user_mail_contact'],
+			'Adres' => "{$userInfo['P_ADDRESS_POST_CODE']} {$userInfo['P_ADDRESS_CITY']} {$userInfo['P_ADDRESS_STREET']} {$userInfo['P_ADDRESS_HOME']}/{$userInfo['P_ADDRESS_HOUSE']}",
+			'Operator' => "[{$userInfo['BILLING_OWNER']}] {$userInfo['BILLING_OWNER_NAME']}",
+		];
+
+		return UI::h('div', [], [
+			UI::h('p', [ 'style' => "border-left:3px solid #ddd; padding-left:6px; font-weight:bold" ], "Dane klienta"),
+			UI::h('table', [ 'class' => "table table-bordered", 'style' => "width:auto; background:#fff" ],
+				array_map(function ($fieldName) use ($outUserInfo) {
+				return UI::h('tr', [], [
+					UI::h('th', [], $fieldName),
+					UI::h('td', [], $outUserInfo[$fieldName]),
+				]);
+				}, array_keys($outUserInfo))
+			),
+		]);
+	}
+	function viewWidgetUserWindykInfo($idUser, $windykInfo = []) {
+		if (empty($windykInfo)) $windykInfo = $this->getUserInfo($idUser);
+
+		$outWindykInfo = [
+			'Status' => $windykInfo['A_STATUS'],
+
+			// $filter_arr['stan_zero'] = array("stan zerowy");
+			// $filter_arr['10-ego'] = array("termin płatności faktur, saldo na -", 'desc'=>array("10-ego", "10-ego każdego miesiąca - termin płatnosci faktur oraz saldo na minusie"));
+			// $filter_arr['WAITING'] = array("termin płatności faktur", 'desc'=>array("10-ego", "10-ego każdego miesiąca - termin płatnosci faktur"));// z regulaminu
+			// $filter_arr['15'] = array("mail ponaglenie", 'desc'=>array("10-ego + 5", "10-ego + 5 dni roboczych"));
+			// $filter_arr['blokada'] = array("blokada", 'desc'=>array("pn, wt, śr około 25", "najbliższy pn, wt, śr do 25"));// najbliższy pn, wt, śr do 25-ego danego miesiąca
+			// $filter_arr['tel1'] = array("kontakt tel.", 'desc'=>array("", "Ostatni kontakt tel. ponad 3 m-ce temu"));
+			// $filter_arr['bad_address'] = array("błędny adres");
+			// $filter_arr['wezwanie1'] = array("wezwanie do zapłaty", 'desc'=>array("2 x FVat", "2 faktury"));
+			// $filter_arr['waiting-wezwanie2'] = array("oczekiwanie 7 dni od terminu płatności");
+			// $filter_arr['wezwanie2'] = array("ostateczne wezwanie do zapłaty, rozwiązanie umowy", 'desc'=>array("wezwanie + 14", "wezwanie + 14 dni"));
+			// $filter_arr['waiting-krd'] = array("min. 30 dni od ostatecznego wezwania");
+			// $filter_arr['krd'] = array("do przekazania do KRD", 'desc'=>array("wezwanie ost. + 60", "wezwanie ost. + 60 dni"));
+			// $filter_arr['waiting-sad'] = array("przekazano do KRD");
+			// $filter_arr['docs-in-sad'] = array("przekazać sprawę do sądu");// waiting-sad = 30 dni
+			// $filter_arr['sad'] = array("przekazano sprawę do sądu");
+
+			// $filter_arr['3 m-ce przed'] = array("3 m-ce przed przedawnieniem");// 3 m-ce przed przedawnieniem
+			// $filter_arr['po-terminie'] = array("po terminie");// 3 lata po dacie platnosci faktur
+			// $filter_arr['has_nr_sad'] = array("nr sprawy sąd");
+			// $filter_arr['has_nr_komornik'] = array("nr sprawy komornik");
+			// $filter_arr['has_ustalenia'] = array("ustalenia z klientem");
+			// $filter_arr['sad_and_komornik'] = array("sąd z komornik");
+			// $filter_arr['sad_bez_komornik'] = array("sąd bez komornik");
+			// $filter_arr['isMovedToVectra'] = array("przeniesieni do Vectra");
+
+			// if ($filter_selected == 'po-terminie') {
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`!='0000-00-00'";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`<DATE_SUB(NOW(), INTERVAL 36 MONTH)";
+			// }
+			// else if ($filter_selected == '3 m-ce przed') {
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`!='0000-00-00'";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`<DATE_SUB(NOW(), INTERVAL 33 MONTH)";
+			// 	$sql_where_and_arr[] = "w.`PAY_DATE`>DATE_SUB(NOW(), INTERVAL 36 MONTH)";
+			// }
+			// else if ($filter_selected == 'tel1') {
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// 	$sql_where_and_arr[] = "w.`A_STATUS` in('wezwanie1', 'waiting-wezwanie2', 'wezwanie2', 'waiting-krd')";
+			// 	$sql_where_and_arr[] = "( w.`LAST_PHONE_STATUS_DATE`='0000-00-00'
+			// 		or w.`LAST_PHONE_STATUS_DATE`<DATE_SUB(NOW(), INTERVAL 3 MONTH) )";
+			// }
+			// else if ($filter_selected == 'bad_address') {
+			// 	$sql_where_and_arr[] = "w.`BAD_ADDRESS`>0";
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// }
+			// else if ($filter_selected == 'stan_zero') {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='WAITING'";
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`>-0.05";
+			// }
+			// else if ($filter_selected == '10-ego') {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='WAITING'";
+			// 	$sql_where_and_arr[] = "w.`PAY_SALDO`<=-0.05";
+			// }
+			// else if ($filter_selected == 'has_nr_sad') {
+			// 	$sql_where_and_arr[] = "w.`NR_SPRAWY_SAD`!=''";
+			// }
+			// else if ($filter_selected == 'has_nr_komornik') {
+			// 	$sql_where_and_arr[] = "w.`NR_SPRAWY_KOMORNIK`!=''";
+			// }
+			// else if ($filter_selected == 'has_ustalenia') {
+			// 	$sql_where_and_arr[] = "w.`L_APPOITMENT_INFO`!=''";
+			// }
+			// else if ($filter_selected == 'sad_and_komornik') {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='sad' and w.`NR_SPRAWY_KOMORNIK`!=''";
+			// }
+			// else if ($filter_selected == 'sad_bez_komornik') {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='sad' and w.`NR_SPRAWY_KOMORNIK`=''";
+			// }
+			// else if ($filter_selected == 'isMovedToVectra') {
+			// 	$sql_where_and_arr[] = "w.`IS_MOVED_TO_VECTRA`=1";
+			// }
+			// else {
+			// 	$sql_where_and_arr[] = "w.`A_STATUS`='{$filter_selected}'";
+			// }
+
+			'Saldo' => $windykInfo['PAY_SALDO'],
+			'Termin ost. fv' => $windykInfo['PAY_DATE'], // Data wymagalności ostatniej faktury
+			'Zaległe faktury' => $windykInfo['PAY_FVAT'],
+			'Adresy usług' => $windykInfo['SERVICES_STREETS'],
+		];
+
+		return UI::h('div', [], [
+			UI::h('p', [ 'style' => "border-left:3px solid #ddd; padding-left:6px; font-weight:bold" ], [
+				"Informacje o płatnościach",
+				($windykInfo['A_STATUS_UPDATE_DATE'] < date("Y-m-d"))
+				?	UI::h('div', [ 'class' => "text-danger" ], [
+						"Uwaga: Wymagają aktualizacji",
+						" ",
+						UI::h('em', [ 'class' => "text-muted" ], "(dane z {$windykInfo['A_STATUS_UPDATE_DATE']})"),
+						// TODO: btn aktualizuj lub auto aktualizuj przed pokazaniem danych
+					])
+				:	"",
+			]),
+			UI::h('table', [ 'class' => "table table-bordered", 'style' => "width:auto; background:#fff" ],
+				array_map(function ($fieldName) use ($outWindykInfo) {
+				return UI::h('tr', [], [
+					UI::h('th', [], $fieldName),
+					UI::h('td', [], $outWindykInfo[$fieldName]),
+				]);
+				}, array_keys($outWindykInfo))
+			),
+			UI::h('table', [ 'class' => "table table-bordered", 'style' => "width:auto; display:none; background:yellow" ],
+				array_map(function ($fieldName) use ($windykInfo) {
+				return UI::h('tr', [], [
+					UI::h('th', [], $fieldName),
+					UI::h('td', [], $windykInfo[$fieldName]),
+				]);
+				}, array_keys($windykInfo))
+			),
+		]);
+	}
+
+	function viewHistContacts($idUser) {
+		$lastHist = DB::getPDO()->fetchAll("
+			select t.ID
+				, t.ID_BILLING_USERS
+		--		, t.CLIENT_INFO
+				, t.A_STATUS
+		--		, t.A_STATUS_INFO
+				, t.L_APPOITMENT_USER
+				, t.L_APPOITMENT_DATE
+				, t.L_APPOITMENT_TYPE
+				, t.L_APPOITMENT_INFO
+			from USERS2_CONTACT t
+			where t.ID_BILLING_USERS = :id
+				and t.A_STATUS != 'DELETED'
+			limit 11
+		", [ ':id' => $idUser ]);
+
+		UI::table([ 'caption' => "Historia kontaktów z klientem (TODO: w trakcie prac. Zobacz " .
+			UI::h('a', [ 'href' => "index.php?MENU_INIT=USERS2_WINDYKACJA_STATUS&q=&_f=&_user_id={$idUser}" ], "Panel windykacji") .
+		")", 'rows' => $lastHist ]);
+		// if (count($lastHist) > 10) UI::alert('info', "Przeglądaj wszystkie wpisy TODO: link to view table with ff_ID_BILLING_USERS");
+	}
+
+	function getContactInfo($id) {
+		return DB::getPDO()->fetchFirst(" select t.* from USERS2_CONTACT t where t.ID = :id ", [ ':id' => $id ]);
+	}
+
+	function getUserInfo($idUser) {
+		return DB::getPDO()->fetchFirst("
+			select bua.`id_users`
+				, bua.`id_users` as ID_BILLING_USERS
+				, bua.`is_firma`
+				, bua.`P_NAME`
+				, bua.`P_NAME_SECOND`
+				, bua.`P_NIP`
+				, bua.`P_PESEL`
+				, bua.`P_PHONE`
+				, bua.`P_ADDRESS_REGION`
+				, bua.`P_ADDRESS_CITY`
+				, bua.`P_ADDRESS_POST_CODE`
+				, bua.`P_ADDRESS_STREET`
+				, bua.`P_ADDRESS_HOME`
+				, bua.`P_ADDRESS_HOUSE`
+				, bua.`user_mail_contact`
+				, bu.`BILLING_OWNER`
+				, ( select bo.name1 from BILLING_OWNER bo where bo.ID = bu.BILLING_OWNER ) as BILLING_OWNER_NAME
+			from `BILLING_USERS_ADD` as bua
+				left join `BILLING_USERS` as bu on ( bu.`ID` = bua.`id_users` )
+			where bua.`id_users` = :id_user
+		", [ ':id_user' => $idUser ]);
+	}
+	function getWindykacjaInfo($idUser) {
+		if ($this->_windykInfo) return $this->_windykInfo;
+
+		$this->_windykInfo = $this->fetchWindykacjaInfo($idUser);
+		if ($this->_windykInfo['A_STATUS_UPDATE_DATE'] < date("Y-m-d")) {
+			Lib::loadClass('Windykacja_StatsModel');
+			Lib::loadClass('Windykacja_StatsHelper');
+			$user = Windykacja_StatsModel::get_user_by_id($idUser);
+			if ($user) {
+				$billing_docs = Windykacja_StatsModel::get_bill_dosc_by_date($user);
+				Windykacja_StatsHelper::update_stats($user, $billing_docs);
+			}
+			$this->_windykInfo = $this->fetchWindykacjaInfo($idUser);
+		}
+		return $this->_windykInfo;
+	}
+	function fetchWindykacjaInfo($idUser) {
+		return DB::getPDO()->fetchFirst("
+			select t.`ID`
+				, t.A_STATUS
+				, t.A_STATUS_INFO
+				, t.*
+			from `USERS2_WINDYKACJA_STATUS` as t
+			where t.`ID_BILLING_USERS` = :id_user
+		", [ ':id_user' => $idUser ]);
+	}
+
+	function prepareTable() {
+		DB::getPDO()->execSql("
+			CREATE TABLE IF NOT EXISTS `USERS2_CONTACT` (
+				`ID` int(11) NOT NULL AUTO_INCREMENT,
+				`ID_BILLING_USERS` int(11) NOT NULL DEFAULT '0',
+				`CLIENT_INFO` varchar(255) NOT NULL DEFAULT '',
+				`A_STATUS` enum('WAITING','NORMAL','DELETED') NOT NULL DEFAULT 'WAITING',
+				`A_STATUS_INFO` varchar(255) NOT NULL,
+				`L_APPOITMENT_DATE` date NOT NULL DEFAULT '0000-00-00',
+				`L_APPOITMENT_USER` varchar(20) NOT NULL DEFAULT '',
+				`L_APPOITMENT_PERIOD` varchar(4) NOT NULL,
+				`L_APPOITMENT_INFO` varchar(255) NOT NULL,
+				`L_APPOITMENT_TYPE` enum('','LIVE','TEL','MAIL','SMS','INNE') DEFAULT NULL,
+				`A_RECORD_CREATE_DATE` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+				`A_RECORD_CREATE_AUTHOR` varchar(20) NOT NULL DEFAULT '',
+				`A_RECORD_UPDATE_DATE` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
+				`A_RECORD_UPDATE_AUTHOR` varchar(20) NOT NULL DEFAULT '',
+				`A_PROBLEM` enum('','WARNING','PROBLEM','SERIOUS','UNVERIFIED') DEFAULT NULL,
+				`A_PROBLEM_DESC` varchar(255) NOT NULL DEFAULT '',
+				`A_PROBLEM_DATE` varchar(30) NOT NULL DEFAULT '',
+				`T_WORKPOINTS` varchar(100) NOT NULL DEFAULT '',
+				`T_WORKPOINTS_VALUE` varchar(100) NOT NULL DEFAULT '',
+				`T_WORKPOINTS_TYPE` varchar(100) NOT NULL DEFAULT '',
+				`T_WORKPOINTS_USER` varchar(100) NOT NULL DEFAULT '',
+				`T_WORKPOINTS_DATE` varchar(100) NOT NULL DEFAULT '',
+				`A_CLASSIFIED` varchar(100) NOT NULL DEFAULT '',
+				`A_ADM_COMPANY` varchar(100) NOT NULL DEFAULT '',
+				`ROZLICZ_MONTH` date NOT NULL DEFAULT '0000-00-00',
+				`ROZLICZ_CONFIRM` int(11) NOT NULL DEFAULT '0',
+				`SALDO` decimal(10,2) NOT NULL DEFAULT '0.00',
+				`ILE_ODZYSKANO` decimal(10,2) NOT NULL DEFAULT '0.00',
+				PRIMARY KEY (`ID`)
+			) ENGINE=MyISAM DEFAULT CHARSET=latin2;
+		");
+		DB::getPDO()->execSql("
+			CREATE TABLE IF NOT EXISTS `USERS2_CONTACT_HIST` (
+				`ID` int(11) NOT NULL AUTO_INCREMENT,
+				`ID_USERS2` int(11) NOT NULL,
+				`ID_BILLING_USERS` varchar(11) NOT NULL DEFAULT 'N/S;',
+				`CLIENT_INFO` varchar(255) NOT NULL DEFAULT 'N/S;',
+				`A_STATUS` varchar(32) NOT NULL DEFAULT 'N/S;',
+				`A_STATUS_INFO` varchar(255) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_DATE` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_USER` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_PERIOD` varchar(4) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_INFO` varchar(255) NOT NULL DEFAULT 'N/S;',
+				`L_APPOITMENT_TYPE` varchar(16) NOT NULL DEFAULT 'N/S;',
+				`A_RECORD_CREATE_DATE` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`A_RECORD_CREATE_AUTHOR` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`A_RECORD_UPDATE_DATE` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`A_RECORD_UPDATE_AUTHOR` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`A_PROBLEM` varchar(16) NOT NULL DEFAULT 'N/S;',
+				`A_PROBLEM_DESC` varchar(255) NOT NULL DEFAULT 'N/S;',
+				`A_PROBLEM_DATE` varchar(30) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS_VALUE` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS_TYPE` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS_USER` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`T_WORKPOINTS_DATE` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`A_CLASSIFIED` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`A_ADM_COMPANY` varchar(100) NOT NULL DEFAULT 'N/S;',
+				`ROZLICZ_MONTH` varchar(20) NOT NULL DEFAULT 'N/S;',
+				`ROZLICZ_CONFIRM` varchar(11) NOT NULL DEFAULT 'N/S;',
+				`SALDO` varchar(11) NOT NULL DEFAULT 'N/S;',
+				`ILE_ODZYSKANO` varchar(11) NOT NULL DEFAULT 'N/S;',
+				PRIMARY KEY (`ID`),
+				KEY `ID_USERS2` (`ID_USERS2`)
+			) ENGINE=MyISAM DEFAULT CHARSET=latin2;
+		");
+	}
+}

+ 24 - 59
SE/se-lib/TableAjax.php

@@ -551,26 +551,11 @@ class TableAjax extends ViewAjax {
 
 	public function ajaxTask($task) {
 		switch ($task) {
-			case 'EDIT_INLINE': {
-				$this->sendAjaxResponseJson('ajaxInlineEdit', $_REQUEST);
-				break;
-			}
-			case 'EDIT_INLINE_SAVE': {
-				$this->sendAjaxResponseJson('ajaxEditInlineSave', $_REQUEST);
-				break;
-			}
-			case 'COPY': {
-				$this->sendAjaxResponseJson('ajaxCopy', $_REQUEST);
-				break;
-			}
-			case 'CREATE_SAVE': { // TODO: mv to _route = ViewTableAjax & _task = createSaveAjax
-				$this->sendAjaxResponseJson('ajaxCreateSave', $_REQUEST);
-				break;
-			}
-			case 'HIST': {
-				$this->sendAjaxResponseJson('ajaxHist', $_REQUEST);
-				break;
-			}
+			case 'EDIT_INLINE': return $this->sendAjaxResponseJson('ajax__EDIT_INLINE__Task', $_REQUEST);
+			case 'EDIT_INLINE_SAVE': return $this->sendAjaxResponseJson('ajax__EDIT_INLINE_SAVE__Task', $_REQUEST);
+			case 'COPY': return $this->sendAjaxResponseJson('ajax__CUPY__Task', $_REQUEST);
+			case 'CREATE_SAVE': return $this->sendAjaxResponseJson('ajax__CREATE_SAVE__Task', $_REQUEST); // TODO: mv to _route = ViewTableAjax & _task = createSaveAjax
+			case 'HIST': return $this->sendAjaxResponseJson('ajax__HIST__Task', $_REQUEST);
 			case 'FILES': {
 				try {
 					$id = V::get('ID', 0, $_REQUEST, 'int');
@@ -587,18 +572,9 @@ class TableAjax extends ViewAjax {
 				}
 				break;
 			}
-			case 'FILES_UPLOAD': {
-				$this->sendAjaxResponseJson('ajaxFileUpload', $_REQUEST);
-				break;
-			}
-			case 'FILES_LIST': {
-				$this->sendAjaxResponseJson('ajaxFileList', $_REQUEST);
-				break;
-			}
-			case 'filePermsRefresh': {
-				$this->sendAjaxResponseJson('ajaxFilePermsRefresh', $_REQUEST);
-				break;
-			}
+			case 'FILES_UPLOAD': return $this->sendAjaxResponseJson('ajax__FILES_UPLOAD__Task', $_REQUEST);
+			case 'FILES_LIST': return $this->sendAjaxResponseJson('ajax__FILES_LIST__Task', $_REQUEST);
+			case 'filePermsRefresh': return $this->sendAjaxResponseJson('ajax__filePermsRefresh__Task', $_REQUEST);
 			case 'FILES_CONN_TBL_LIST': {
 				$id = V::get('ID', 0, $_REQUEST, 'int');
 				if ($id > 0) {
@@ -617,20 +593,10 @@ class TableAjax extends ViewAjax {
 				}
 				break;
 			}
-			case 'HIDDEN_COLS_SAVE': {
-				$this->sendAjaxResponseJson('ajaxHiddenColsSave', $_POST);
-				break;
-			}
-			case 'PAGE_SIZE_SAVE': {
-				$this->sendAjaxResponseJson('ajaxPageSizeSave', $_POST);
-				break;
-			}
-			case 'THE_GEOM_SAVE': {
-				$this->sendAjaxResponseJson('ajaxTheGeomSave', $_REQUEST);
-				break;
-			}
-			default:
-				$this->sendAjaxResponseJson('ajaxData', $_REQUEST);
+			case 'HIDDEN_COLS_SAVE': return $this->sendAjaxResponseJson('ajax__HIDDEN_COLS_SAVE__Task', $_POST);
+			case 'PAGE_SIZE_SAVE': return $this->sendAjaxResponseJson('ajax__PAGE_SIZE_SAVE__Task', $_POST);
+			case 'THE_GEOM_SAVE': return $this->sendAjaxResponseJson('ajax__THE_GEOM_SAVE__Task', $_REQUEST);
+			default: return $this->sendAjaxResponseJson('ajaxData', $_REQUEST);
 		}
 	}
 
@@ -639,7 +605,7 @@ class TableAjax extends ViewAjax {
 	 * @param $rowID - $_GET['ID']
 	 * @param $fieldName - $_GET['col']
 	 */
-	private function ajaxInlineEdit() {
+	private function ajax__EDIT_INLINE__Task() { // TODO: convert to fetch gui as struct (not raw html) like editFormJson
 		$DBG = ('1' == V::get('DBG', '', $_REQUEST));
 
 		$args = $_REQUEST;
@@ -737,7 +703,7 @@ class TableAjax extends ViewAjax {
 		return $response;
 	}
 
-	private function ajaxEditInlineSave($args) {
+	private function ajax__EDIT_INLINE_SAVE__Task($args) {
 		$primaryKeyField = $this->_acl->getPrimaryKeyField();
 		$primaryKey = V::get($primaryKeyField, 0, $args, 'int');
 		$fieldName = V::get('col', '', $_REQUEST);
@@ -1217,8 +1183,7 @@ jQuery(document).ready(function(){
 		exit;
 	}
 
-	private function ajaxCreateSave($args) {
-		DBG::log($args, 'array', "ajaxCreateSave");
+	private function ajax__CREATE_SAVE__Task($args) {
 		$acl = $this->_acl;
 		$response = new stdClass();
 		$createdId = null;
@@ -1246,7 +1211,7 @@ jQuery(document).ready(function(){
 		return $response;
 	}
 
-	private function ajaxCopy($args) {
+	private function ajax__CUPY__Task($args) {
 		$acl = $this->_acl;
 		$id = V::get('ID', 0, $_REQUEST, 'int');
 		if ($id <= 0) {
@@ -1285,7 +1250,7 @@ jQuery(document).ready(function(){
 		return $response;
 	}
 
-	private function ajaxHist($args) {
+	private function ajax__HIST__Task($args) {
 		$jsonResponse = array();
 		$id = V::get('ID', 0, $args, 'int');
 		$acl = $this->_acl;
@@ -1354,7 +1319,7 @@ jQuery(document).ready(function(){
 		return $jsonResponse;
 	}
 
-	public function ajaxFileUpload($args) {
+	public function ajax__FILES_UPLOAD__Task($args) {
 		$id = V::get('ID', 0, $args, 'int');
 		if ($id <= 0) throw new HttpException("404", 404);
 		$dbID = $this->_acl->getDB();
@@ -1402,7 +1367,7 @@ jQuery(document).ready(function(){
 		return $retJson;
 	}
 
-	public function ajaxFileList($args) {
+	public function ajax__FILES_LIST__Task($args) {
 		$id = V::get('ID', 0, $args, 'int');
 		if ($id <= 0) throw new HttpException("404", 404);
 		$dbID = $this->_acl->getDB();
@@ -1428,7 +1393,7 @@ jQuery(document).ready(function(){
 		return $jsonFiles;
 	}
 
-	private function ajaxFilePermsRefresh($args) {
+	private function ajax__filePermsRefresh__Task($args) {
 		$id = V::get('ID', 0, $args, 'int');
 		if ($id <= 0) throw new HttpException("Wrong param ID", 404);
 
@@ -2240,7 +2205,7 @@ jQuery(document).ready(function(){
 				}
 			}
 
-			// @see ajaxHiddenColsSave
+			// @see ajax__HIDDEN_COLS_SAVE__Task
 			if (UserProfile::isHiddenColumn($acl->getID(), $fieldID)
 				|| ($gui__hideRefFields && 'ref:' === substr($columnConfig->xsdType, 0, strlen('ref:')))
 			) {
@@ -2457,7 +2422,7 @@ jQuery(document).ready(function(){
 	 * set hidden cols in $_SESSION['USER_PROFILE'][$this->_zasobID];
 	 *   $_SESSION['USER_PROFILE'][$this->_zasobID][fld_id] => boolean
 	 */
-	private function ajaxHiddenColsSave($args) { // $args[ idField ] => 'SHOW' | 'HIDE'
+	private function ajax__HIDDEN_COLS_SAVE__Task($args) { // $args[ idField ] => 'SHOW' | 'HIDE'
 		$response = new stdClass();
 
 		if (empty($args)) {
@@ -2489,7 +2454,7 @@ jQuery(document).ready(function(){
 		return $response;
 	}
 
-	private function ajaxPageSizeSave($args) {
+	private function ajax__PAGE_SIZE_SAVE__Task($args) {
 		$response = new stdClass();
 
 		if (empty($args)) {
@@ -2515,7 +2480,7 @@ jQuery(document).ready(function(){
 		return $response;
 	}
 
-	private function ajaxTheGeomSave($args) {
+	private function ajax__THE_GEOM_SAVE__Task($args) {
 		$primaryKeyField = $this->_acl->getPrimaryKeyField();
 		$primaryKey = V::get($primaryKeyField, 0, $args, 'int');
 		$polygon = V::get('polygon', 0, $args);

+ 1 - 1
SE/se-lib/TableAjax.php.TableAjax.js

@@ -3406,7 +3406,7 @@ var TableAjax = function() {
 	/**
 	 * Inline edit.
 	 */
-	priv.rowDblClicked = function(e) {
+	priv.rowDblClicked = function(e) { // TODO: export outside (handle in table router file)
 		var inlineEditBox$Node = $(_uiNodeCont).parent().children('.tblAjax__inlineEditBox');
 
 		// hide popover for typespecial fld on click