Upload markdown files and organize them in a tree structure
⚠️ Warning: This action cannot be undone!
controllers/clientController.php (1,805 lines) - Updated Version
client
// API Mode Check - Lines 6-29
">if (isset(">$_GET['api_mode039;]) && $_GET[039;api_mode039;] == 039;1039;) {
header('Content-Type: application/json; charset=utf-8039;);
">$action = isset(">$_GET['action039;]) ? $_GET[039;action039;] : 039;test039;;
">if ($action == 'test039;) {
echo json_encode(array(
'status039; => 039;success039;,
'message039; => 039;API working via existing controller039;,
'timestamp039; => date(039;Y-m-d H:i:s039;),
'user_id039; => isset($_SESSION[039;userid039;]) ? $_SESSION[039;userid039;] : null
));
exit;
}
">if ($action == 'list039;) {
$clients = R::getAll('SELECT clientid, clientname, clientmobile FROM client WHERE conditions = 0 LIMIT 10039;);
echo json_encode(array(
'status039; => 039;success039;,
'data039; => $clients
));
exit;
}
}
initiateStaticSessionCommingWithCurl.php (Line 33)
// CURL POST detection throughout controller
">if (isset($_POST['curlpost039;]) && $_POST[039;curlpost039;] == 1) {
// JSON response for external systems
$data = array('status039; => 039;success039;, 039;status_code039; => 200, 039;message039; => $httpStatusCodes[200]);
">echo json_encode($data);
} else {
// Standard web response
header("location:?do=sucess");
}
$ajaxDoArr array
">elseif ($do == "addSimpleReturn") { //special for link with supplier
try {
$id = add();
">if ($id == -1) {
//there is client with same name => get its id
">$checkName = $clientDAO->queryByClientname($_POST['txtName039;]);
">$id = ">$clientId = $checkName[0]->clientid;
$supplierId = (int) $_POST["linkedSupplierId"];
//link
//remove any link to same suppplier
$clientExt->removeAnyClientLinkForASupplier($supplierId);
$checkName[0]->linkedSupplierId = $supplierId;
">$clientDAO->update($checkName[0]);
}
">echo $id; //it is client id
} ">catch (Exception $e) {
header("location:?do=error");
}
}
savedata operation
deleteFinaly operation
delete operation
updatedata operation
client
CREATE TABLE client (
clientid INT PRIMARY KEY AUTO_INCREMENT,
clientname VARCHAR(256) NOT NULL,
clientaddress VARCHAR(256),
clientaddress2 VARCHAR(256),
country VARCHAR(256),
clientphone VARCHAR(20),
clientmobile VARCHAR(20),
clientdebt DECIMAL(10,2) NOT NULL DEFAULT 0,
clientdetails TEXT,
conditions INT NOT NULL DEFAULT 0, -- 0=active, 1=suspended
clientdate DATE NOT NULL,
userid INT NOT NULL,
branchId INT DEFAULT 0,
clientareaid INT NOT NULL DEFAULT 0,
clientcode VARCHAR(255),
dailyentryid INT NOT NULL DEFAULT 0,
clientStoreIds VARCHAR(255) NOT NULL DEFAULT '0039;,
obygyPatientId INT,
debtLimit DECIMAL(10,2) NOT NULL DEFAULT 0,
typeclientid VARCHAR(255) NOT NULL DEFAULT '0039;,
priceTypeId INT NOT NULL DEFAULT 0,
lastEditUser INT NOT NULL DEFAULT 0,
inUse TINYINT NOT NULL DEFAULT 0,
specialDiscount TINYINT NOT NULL DEFAULT 0,
specialDiscountVal FLOAT NOT NULL DEFAULT 0,
file VARCHAR(255) NOT NULL,
addDate DATE NOT NULL,
mandobCollectRatio FLOAT NOT NULL DEFAULT 0,
webApiId INT NOT NULL DEFAULT 0,
clientRFID VARCHAR(20),
linkedSupplierId INT NOT NULL DEFAULT 0,
postponeDays INT NOT NULL DEFAULT 0,
taxnumber VARCHAR(255),
password VARCHAR(256) NOT NULL,
clientTypeForTree TINYINT NOT NULL DEFAULT 0,
treeId INT NOT NULL DEFAULT 0,
txtNameE VARCHAR(256) NOT NULL,
facility VARCHAR(256) NOT NULL,
delegate VARCHAR(256) NOT NULL,
txtemail VARCHAR(256) NOT NULL,
commercial VARCHAR(256) NOT NULL,
valtaxnumber VARCHAR(256) NOT NULL,
delegateid INT NOT NULL DEFAULT 0,
-- Geographic coordinates
vlat VARCHAR(255),
vlong VARCHAR(255),
vimage VARCHAR(255)
);
clientdebtchange - Debt History
CREATE TABLE clientdebtchange (
clientdebtchangeid INT PRIMARY KEY AUTO_INCREMENT,
clientid INT NOT NULL,
clientdebtchangebefore DECIMAL(10,3) NOT NULL,
clientdebtchangeamount DECIMAL(10,3) NOT NULL,
clientdebtchangetype INT NOT NULL DEFAULT 0, -- 0=plus, 1=minus
processname VARCHAR(500) NOT NULL,
clientdebtchangemodelid INT NOT NULL,
clientdebtchangeafter DECIMAL(10,3) NOT NULL,
clientdebtchangedate DATETIME NOT NULL,
userid INT NOT NULL,
tablename VARCHAR(256) NOT NULL,
comment TEXT,
totalOperationCost DECIMAL(10,2) NOT NULL DEFAULT 0,
discount DECIMAL(10,2) DEFAULT 0,
dailyentryid INT NOT NULL DEFAULT 0,
-- Additional tracking fields
billid INT,
paytype VARCHAR(255),
costcenterid INT NOT NULL DEFAULT 0,
currencyId INT NOT NULL
);
clientarea - Geographic Areas
CREATE TABLE clientarea (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
comment TEXT NOT NULL,
webApiId INT NOT NULL DEFAULT 0,
associatedtag_id INT NOT NULL DEFAULT 0
);
tamweenclientdetail - Support Program Details
CREATE TABLE tamweenclientdetail (
id INT PRIMARY KEY AUTO_INCREMENT,
clientid INT NOT NULL,
noOfPersonsTamween INT NOT NULL,
noOfPersonsDa3m INT NOT NULL,
cardNum VARCHAR(30) NOT NULL,
cardPassword VARCHAR(6) NOT NULL
);
dailyentry - Journal Entry Header
CREATE TABLE dailyentry (
id INT PRIMARY KEY AUTO_INCREMENT,
totalcreditor DECIMAL(10,2) NOT NULL, -- total credit amount
totaldebtor DECIMAL(10,2) NOT NULL, -- total debit amount
thedate DATE NOT NULL, -- journal entry date
userid INT NOT NULL, -- user who created entry
condition TINYINT NOT NULL DEFAULT 0, -- 0=active, 1=deleted
reverseofid INT NOT NULL DEFAULT 0, -- ID of reversed entry
dDateTime DATETIME NOT NULL, -- creation timestamp
entryComment TEXT, -- journal entry description
fromFlag INT NOT NULL, -- 0=manual, 1=auto from program, 2=plugin
related INT NOT NULL, -- groups related entries
branchid INT DEFAULT 0, -- branch reference
operationId INT NOT NULL, -- source transaction ID (clientid for clients)
operationDetailLink TEXT NOT NULL -- link to source transaction
);
dailyentrycreditor - Journal Entry Credit Lines
CREATE TABLE dailyentrycreditor (
id INT PRIMARY KEY AUTO_INCREMENT,
dailyentryid INT NOT NULL, -- parent journal entry
accountstreeid INT NOT NULL, -- credit account from chart of accounts
value DECIMAL(10,2) NOT NULL, -- credit amount
dComment VARCHAR(300) NOT NULL, -- line description
costcenterid INT NOT NULL -- cost center allocation
);
dailyentrydebtor - Journal Entry Debit Lines
CREATE TABLE dailyentrydebtor (
id INT PRIMARY KEY AUTO_INCREMENT,
dailyentryid INT NOT NULL, -- parent journal entry
accountstreeid INT NOT NULL, -- debit account from chart of accounts
value DECIMAL(10,2) NOT NULL, -- debit amount
dComment VARCHAR(300) NOT NULL, -- line description
costcenterid INT NOT NULL -- cost center allocation
);
add() function)
-- Check for duplicate client name
SELECT * FROM client WHERE clientname = ?
-- Insert new client (via DAO)
INSERT INTO client (
clientname, clientaddress, clientaddress2, country, clientphone, clientmobile,
clientdebt, clientdetails, conditions, clientdate, userid, clientareaid,
clientcode, dailyentryid, clientStoreIds, debtLimit, postponeDays,
typeclientid, priceTypeId, mandobCollectRatio, specialDiscount,
specialDiscountVal, file, addDate, webApiId, clientRFID, linkedSupplierId,
taxnumber, password, clientTypeForTree, txtNameE, facility, delegate,
txtemail, commercial, valtaxnumber, delegateid, obygyPatientId
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-- Insert client debt change history
INSERT INTO clientdebtchange (
clientid, clientdebtchangebefore, clientdebtchangeamount,
clientdebtchangeafter, clientdebtchangetype, clientdebtchangedate,
clientdebtchangemodelid, processname, tablename, userid,
clientareaid, dailyentryid, totalOperationCost, comment
) VALUES (?, 0, ?, ?, 0, ?, ?, 'إضافة عميل جديد039;, 039;clientController.php039;, ?, ?, 0, ?, 039;039;)
-- Insert support program details (Tamween)
INSERT INTO tamweenclientdetail (
clientid, noOfPersonsTamween, noOfPersonsDa3m, cardNum, cardPassword
) VALUES (?, ?, ?, ?, ?)
-- Insert client additional details (if enabled)
INSERT INTO clientdetails (
clientid, booking_date, rehearsal_date, shoulder, how_much, jacket_length,
jacket_expand, sleeve_head, pants_beam, pants_stone, pants_hash, pants_knee,
pants_thigh, pants_man, samanah, pants_length, sudairi_belly, sudairi_chest,
sudairi_length, size_jackt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-- Insert electronic invoice settings (if enabled)
INSERT INTO eclientsetting (
clientid, egovernorate, ecity, estreet, ebuildingNum, etaxType, etaxNum, clientname
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
-- CRITICAL: Create Daily Entry (Journal Entry) for Client Creation
INSERT INTO dailyentry (
totalcreditor, totaldebtor, thedate, userid, condition, reverseofid,
dDateTime, entryComment, fromFlag, related, branchid, isopeningentry,
operationId, operationDetailLink
) VALUES (?, ?, ?, ?, 0, 0, ?, 'قيد يومية لإضافة عميل039;, 1, 0, ?, 0, ?, ?)
SET @dailyentry_id = LAST_INSERT_ID();
-- If client has initial debt (debit customer account)
INSERT INTO dailyentrydebtor (
dailyentryid, accountstreeid, value, dComment, costcenterid
) VALUES (@dailyentry_id, ?, ?, 'رصيد افتتاحي عميل039;, ?)
-- Credit opening balance account
INSERT INTO dailyentrycreditor (
dailyentryid, accountstreeid, value, dComment, costcenterid
) VALUES (@dailyentry_id, ?, ?, 'رصيد افتتاحي عميل039;, ?)
-- Update client with tree and daily entry IDs
UPDATE client SET dailyentryid = @dailyentry_id, treeId = ? WHERE clientid = ?
show() functions)
-- Get all active clients with user details
SELECT client.*, user.employeename, lasteditone.employeename as lastEditUserName,
deligate.employeename as deligateName
FROM client
LEFT JOIN user ON user.userid = client.userid
LEFT JOIN user AS lasteditone ON lasteditone.userid = client.lastEditUser
LEFT JOIN user AS deligate ON deligate.userid = client.delegateid
WHERE client.conditions = 0
ORDER BY clientid ASC
-- Get suspended clients
SELECT client.*, user.employeename, lasteditone.employeename as lastEditUserName
FROM client
LEFT JOIN user ON user.userid = client.userid
LEFT JOIN user AS lasteditone ON lasteditone.userid = client.lastEditUser
WHERE client.conditions = 1
ORDER BY clientid ASC
-- Search clients by name/phone/mobile/code
SELECT clientname, clientid, clientmobile, clientdebt
FROM client
WHERE (clientname LIKE "%?%"
OR clientphone LIKE "?%"
OR clientmobile LIKE "?%"
OR clientcode LIKE "?%")
AND conditions = 0
ORDER BY clientdate DESC, clientid DESC
-- Get clients by area with area name
SELECT client.*, clientarea.name as clientareaName
FROM client
JOIN clientarea ON clientarea.id = client.clientareaid
WHERE client.clientareaid = ?
ORDER BY clientid DESC
update() function)
-- Load existing client data
SELECT * FROM client WHERE clientid = ?
-- Update client (via DAO updateButNotDept method)
UPDATE client SET
clientname = ?, clientaddress = ?, clientaddress2 = ?, country = ?,
clientphone = ?, clientmobile = ?, clientdetails = ?, conditions = ?,
clientareaid = ?, debtLimit = ?, postponeDays = ?, priceTypeId = ?,
mandobCollectRatio = ?, specialDiscount = ?, specialDiscountVal = ?,
file = ?, clientStoreIds = ?, typeclientid = ?, lastEditUser = ?,
clientRFID = ?, taxnumber = ?, password = ?, clientTypeForTree = ?,
txtNameE = ?, facility = ?, delegate = ?, txtemail = ?, commercial = ?,
valtaxnumber = ?, delegateid = ?, treeId = ?, obygyPatientId = ?
WHERE clientid = ?
-- Update or insert Tamween details
-- If exists:
UPDATE tamweenclientdetail SET
noOfPersonsTamween = ?, noOfPersonsDa3m = ?, cardNum = ?, cardPassword = ?
WHERE clientid = ?
-- If not exists:
INSERT INTO tamweenclientdetail (clientid, noOfPersonsTamween, noOfPersonsDa3m, cardNum, cardPassword)
VALUES (?, ?, ?, ?, ?)
-- Update client details (if enabled)
UPDATE clientdetails SET
booking_date = ?, rehearsal_date = ?, shoulder = ?, how_much = ?,
jacket_length = ?, jacket_expand = ?, sleeve_head = ?, pants_beam = ?,
pants_stone = ?, pants_hash = ?, pants_knee = ?, pants_thigh = ?,
pants_man = ?, samanah = ?, pants_length = ?, sudairi_belly = ?,
sudairi_chest = ?, sudairi_length = ?, size_jackt = ?
WHERE id = ?
-- Update electronic invoice settings
UPDATE eclientsetting SET
egovernorate = ?, ecity = ?, estreet = ?, ebuildingNum = ?,
etaxType = ?, etaxNum = ?, clientname = ?
WHERE id = ?
-- Soft delete (tempdelete)
UPDATE client SET conditions = 1 WHERE clientid = ?
-- Restore deleted client
UPDATE client SET conditions = 0 WHERE clientid = ?
-- Permanent delete preparation
SELECT * FROM client WHERE clientid = ?
DELETE FROM tamweenclientdetail WHERE clientid = ?
DELETE FROM client WHERE clientid = ?
-- Get all delegates (sales representatives)
SELECT user.* FROM user WHERE usergroupid = 2
-- Get client by RFID
SELECT * FROM client WHERE clientRFID = ? AND conditions = 0
-- Get client by code
SELECT * FROM client WHERE clientcode = ?
-- Get single client with details
SELECT * FROM client WHERE clientid = ?
-- Find client details
SELECT * FROM clientdetails WHERE clientid = ?
-- Find electronic settings
SELECT * FROM eclientsetting WHERE clientid = ?
-- Count clients with same address (for duplicate checking)
SELECT count(*) FROM client WHERE clientaddress = ?
-- Get debt limit update
UPDATE client SET debtLimit = ?
-- Update debt only
UPDATE client SET clientdebt = ?, userid = ? WHERE clientid = ?
-- Get all client types
SELECT * FROM typeclient ORDER BY typeId
-- Get clients by type
SELECT * FROM client WHERE typeclientid LIKE "%,?,%"
-- Get all client areas
SELECT * FROM clientarea ORDER BY id
-- Get clients by area
SELECT client.*, clientarea.name as clientareaName
FROM client
JOIN clientarea ON clientarea.id = client.clientareaid
WHERE client.clientareaid = ?
-- Paginated client listing
SELECT * FROM client
ORDER BY clientid ASC
LIMIT ?, ?
-- Limited search results
SELECT clientname, clientid, clientmobile, clientdebt
FROM client
WHERE clientname LIKE "%?%" AND conditions = 0
ORDER BY clientdate DESC, clientid DESC
LIMIT 50
txtName: Client name (required, unique)
txtNameE: English name
txtAddress, txtAddress2: Addresses
country: Country
txtPhone, txtMobile: Contact numbers
txtDebt: Initial debt amount
textDetails: Additional details
clientcode: Client code/ID
txtemail: Email address
clientareaid: Geographic area
debtLimit: Credit limit
postponeDays: Payment grace period
priceTypeId: Pricing tier
mandobCollectRatio: Collection percentage
specialDiscount: Special discount flag
specialDiscountVal: Discount value
clientRFID: RFID card number
linkedSupplierId: Linked supplier
taxnumber: Tax identification
store_all: Access to all stores flag
store{id}: Individual store access
typeClient_all: All client types flag
type{id}: Individual type assignments
noOfPersonsTamween: Support beneficiaries count
noOfPersonsDa3m: Additional support count
cardNum: Program card number
cardPassword: Card PIN
/api/v1/clients
GET /api/v1/clients
Query Parameters:
page: Page number (default: 1)
limit: Items per page (default: 20, max: 100)
search: Search in name/phone/mobile
area_id: Filter by client area
type_id: Filter by client type
conditions: Filter by status (0=active, 1=suspended)
date_from: Filter by creation date (YYYY-MM-DD)
date_to: Filter by creation date (YYYY-MM-DD)
user_id: Filter by creator
has_debt: Boolean, clients with debt only
debt_min: Minimum debt amount
debt_max: Maximum debt amount
store_id: Clients with access to specific store
{
"success": true,
"data": [
{
"clientid": 1,
"clientname": "Ahmed Mohamed",
"txtNameE": "Ahmed Mohamed",
"clientphone": "01234567890",
"clientmobile": "01234567891",
"clientaddress": "Cairo, Egypt",
"clientaddress2": "Apt 5, Building 10",
"country": "Egypt",
"clientdebt": 1500.00,
"debtLimit": 5000.00,
"postponeDays": 30,
"conditions": 0,
"clientdate": "2024-01-15",
"addDate": "2024-01-15",
"clientcode": "C001",
"clientareaid": 5,
"clientarea": {
"id": 5,
"name": "Cairo Central"
},
"priceTypeId": 1,
"specialDiscount": 1,
"specialDiscountVal": 5.0,
"clientStoreIds": "-10", // -10 = all stores
"typeclientid": ",1,3,",
"clientTypes": [
{"typeId": 1, "typeName": "Retail"},
{"typeId": 3, "typeName": "VIP"}
],
"txtemail": "ahmed@example.com",
"file": "client_123.jpg",
"linkedSupplierId": 0,
"taxnumber": "123456789",
"created_by": 1,
"lastEditUser": 2,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-16T14:22:00Z"
}
],
"pagination": {
"current_page": 1,
"total_pages": 25,
"total_items": 487,
"per_page": 20
},
"summary": {
"total_debt": 125000.50,
"active_clients": 450,
"suspended_clients": 37
}
}
GET /api/v1/clients/{id}
Response:
{
"success": true,
"data": {
"clientid": 1,
"clientname": "Ahmed Mohamed",
"txtNameE": "Ahmed Mohamed",
"clientphone": "01234567890",
"clientmobile": "01234567891",
"clientaddress": "Cairo, Egypt",
"clientaddress2": "Apt 5, Building 10",
"country": "Egypt",
"clientdebt": 1500.00,
"debtLimit": 5000.00,
"postponeDays": 30,
"conditions": 0,
"clientdetails": "VIP customer with special requirements",
"clientdate": "2024-01-15",
"addDate": "2024-01-15",
"clientcode": "C001",
"clientarea": {
"id": 5,
"name": "Cairo Central"
},
"priceTypeId": 1,
"mandobCollectRatio": 10.0,
"specialDiscount": 1,
"specialDiscountVal": 5.0,
"stores": [
{"storeId": 1, "storeName": "Main Store"},
{"storeId": 3, "storeName": "Branch Store"}
],
"clientTypes": [
{"typeId": 1, "typeName": "Retail"},
{"typeId": 3, "typeName": "VIP"}
],
"txtemail": "ahmed@example.com",
"commercial": "Ahmed Trading Co.",
"taxnumber": "123456789",
"valtaxnumber": "VAT123456",
"facility": "Trading Facility",
"delegate": "Mohamed Ali",
"delegateid": 15,
"file": "client_123.jpg",
"clientRFID": "RF12345",
"linkedSupplierId": 0,
"password": "*", // Never return actual password
"clientTypeForTree": 0,
"treeId": 500,
"supportDetails": {
"noOfPersonsTamween": 4,
"noOfPersonsDa3m": 2,
"cardNum": "1234567890",
"cardPassword": "*"
},
"accounting": {
"dailyentryid": 1025,
"treeId": 500
},
"created_by": 1,
"lastEditUser": 2,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-16T14:22:00Z"
}
}
POST /api/v1/clients
Content-Type: multipart/form-data
Request Body:
{
"clientname": "New Client Name",
"txtNameE": "New Client Name EN",
"clientphone": "01234567890",
"clientmobile": "01234567891",
"clientaddress": "Address Line 1",
"clientaddress2": "Address Line 2",
"country": "Egypt",
"clientdebt": 0.00,
"debtLimit": 5000.00,
"postponeDays": 30,
"clientdetails": "Additional client details",
"clientcode": "C002",
"clientareaid": 5,
"priceTypeId": 1,
"mandobCollectRatio": 0.0,
"specialDiscount": 0,
"specialDiscountVal": 0.0,
"store_all": 1, // 1 for all stores, 0 for specific
"store_ids": [1, 3], // if not store_all
"typeClient_all": 0,
"client_type_ids": [1, 3],
"txtemail": "client@example.com",
"commercial": "Client Business",
"taxnumber": "TAX123456",
"valtaxnumber": "VAT123456",
"facility": "Business Facility",
"delegateid": 15,
"clientTypeForTree": 0, // 0=normal, 1=special
"supportDetails": {
"noOfPersonsTamween": 0,
"noOfPersonsDa3m": 0,
"cardNum": "",
"cardPassword": ""
},
"file": "base64_encoded_file_or_file_upload"
}
Response:
{
"success": true,
"message": "Client created successfully",
"data": {
"clientid": 125,
"clientname": "New Client Name",
"clientcode": "C002",
"clientdebt": 0.00,
"treeId": 501,
"dailyentryid": 1026,
"webApiId": 125,
"created_at": "2024-01-20T09:15:00Z"
}
}
PUT /api/v1/clients/{id}
Content-Type: multipart/form-data
Request Body: (Same as create, plus)
{
"conditions": 0, // 0=active, 1=suspended
"lastEditUser": 2,
// ... other fields same as create
}
Response:
{
"success": true,
"message": "Client updated successfully",
"data": {
"clientid": 125,
"clientname": "Updated Client Name",
"updated_at": "2024-01-20T11:30:00Z",
"lastEditUser": 2
}
}
DELETE /api/v1/clients/{id}
Response:
{
"success": true,
"message": "Client temporarily deleted successfully"
}
POST /api/v1/clients/{id}/restore
Response:
{
"success": true,
"message": "Client restored successfully"
}
DELETE /api/v1/clients/{id}/permanent
Response:
{
"success": true,
"message": "Client permanently deleted"
}
GET /api/v1/clients/{id}/debt-history
Query Parameters:
page: Page number
limit: Items per page
date_from: Filter by date
date_to: Filter by date
{
"success": true,
"data": [
{
"clientdebtchangeid": 1,
"clientdebtchangebefore": 0.00,
"clientdebtchangeamount": 1500.00,
"clientdebtchangeafter": 1500.00,
"clientdebtchangetype": 0, // 0=plus, 1=minus
"processname": "Sale Invoice #INV-001",
"clientdebtchangedate": "2024-01-15T14:30:00Z",
"userid": 1,
"username": "Admin User",
"tablename": "sellbill",
"comment": "Product sale",
"totalOperationCost": 1500.00,
"discount": 0.00,
"billid": 1,
"paytype": "credit"
}
],
"pagination": {
"current_page": 1,
"total_pages": 3,
"total_items": 45,
"per_page": 20
}
}
POST /api/v1/clients/bulk-import
Content-Type: multipart/form-data
Request:
file: Excel file (.xlsx, .xls)
noDailyEntry: Boolean, skip accounting entries
{
"success": true,
"message": "Bulk import completed",
"data": {
"imported": 150,
"skipped": 5,
"errors": 2,
"details": [
{"row": 3, "error": "Duplicate client name"},
{"row": 7, "error": "Invalid phone number"}
]
}
}
GET /api/v1/clients/export/phones
Response:
{
"success": true,
"data": {
"all_phones": "01234567890,01234567891,01234567892",
"mobile_only": "01234567890,01234567891",
"total_phones": 245,
"total_mobiles": 189
}
}
GET /api/v1/clients/search
Query Parameters:
q: Search query
fields: Comma-separated fields to search (name,phone,mobile,email,address)
limit: Maximum results (default: 50)
{
"success": true,
"data": [
{
"clientid": 1,
"clientname": "Ahmed Mohamed",
"clientphone": "01234567890",
"clientmobile": "01234567891",
"clientdebt": 1500.00
}
]
}
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The given data was invalid",
"details": {
"clientname": ["Client name is required"],
"clientphone": ["Phone number format is invalid"],
"clientdebt": ["Debt amount must be numeric"]
}
}
}
// Client creation validation
$rules = [
'clientname039; => 039;required|string|max:256|unique:client,clientname039;,
'clientphone039; => 039;nullable|string|max:20|regex:/^[0-9+\-\s()]+$/039;,
'clientmobile039; => 039;nullable|string|max:20|regex:/^[0-9+\-\s()]+$/039;,
'clientdebt039; => 039;numeric|min:-999999.99|max:999999.99039;,
'debtLimit039; => 039;numeric|min:0|max:999999.99039;,
'clientareaid039; => 039;required|exists:clientarea,id039;,
'clientcode039; => 039;nullable|string|max:255|unique:client,clientcode039;,
'txtemail039; => 039;nullable|email|max:256039;,
'taxnumber039; => 039;nullable|string|max:255039;
];
// Business rule validations
">if (">$clientdebt < 0 && !$allowNegativeDebt) {
throw new ValidationException('Negative debt not allowed039;);
}
">if (">$debtLimit > 0 && ">$clientdebt > $debtLimit) {
throw new ValidationException('Initial debt exceeds credit limit039;);
}
-- Base query with user joins (from queryAllForShow())
SELECT client.*, user.employeename, lasteditone.employeename as lastEditUserName,
deligate.employeename as deligateName
FROM client
LEFT JOIN user ON user.userid = client.userid
LEFT JOIN user AS lasteditone ON lasteditone.userid = client.lastEditUser
LEFT JOIN user AS deligate ON deligate.userid = client.delegateid
WHERE client.conditions = 0
ORDER BY clientid ASC
LIMIT ? OFFSET ?
-- With filters (extend WHERE clause):
-- Filter by area: AND client.clientareaid = ?
-- Filter by type: AND client.typeclientid LIKE "%,?,%"
-- Filter by debt range: AND client.clientdebt BETWEEN ? AND ?
-- Search: AND (client.clientname LIKE "%?%" OR client.clientphone LIKE "%?%" OR client.clientmobile LIKE "%?%")
BEGIN TRANSACTION;
-- Check duplicate name
SELECT COUNT(*) FROM client WHERE clientname = ?;
-- Insert main client record
INSERT INTO client (clientname, clientphone, clientmobile, clientdebt, ...) VALUES (...);
SET @client_id = LAST_INSERT_ID();
-- Insert debt change history
INSERT INTO clientdebtchange (clientid, clientdebtchangeamount, processname, ...)
VALUES (@client_id, ?, 'إضافة عميل جديد039;, ...);
-- Insert support details
INSERT INTO tamweenclientdetail (clientid, noOfPersonsTamween, ...) VALUES (@client_id, ...);
-- Insert client details if enabled
IF (@client_detail_enabled) THEN
INSERT INTO clientdetails (clientid, booking_date, ...) VALUES (@client_id, ...);
END IF;
-- Update with accounting IDs (after tree creation)
UPDATE client SET dailyentryid = ?, treeId = ? WHERE clientid = @client_id;
COMMIT;
BEGIN TRANSACTION;
-- Load existing data
SELECT * FROM client WHERE clientid = ?;
-- Update main client record (via updateButNotDept)
UPDATE client SET
clientname = ?, clientphone = ?, clientmobile = ?, clientdebt = ?,
lastEditUser = ?, ...
WHERE clientid = ?;
-- Update or insert tamween details
SELECT COUNT(*) FROM tamweenclientdetail WHERE clientid = ?;
-- If exists:
UPDATE tamweenclientdetail SET noOfPersonsTamween = ?, ... WHERE clientid = ?;
-- If not exists:
INSERT INTO tamweenclientdetail (clientid, ...) VALUES (?, ...);
-- Update additional details if enabled
UPDATE clientdetails SET booking_date = ?, ... WHERE clientid = ?;
COMMIT;
-- Main client data
SELECT * FROM client WHERE clientid = ?;
-- Support details
SELECT * FROM tamweenclientdetail WHERE clientid = ?;
-- Additional details (if enabled)
SELECT * FROM clientdetails WHERE clientid = ?;
-- Electronic invoice settings (if enabled)
SELECT * FROM eclientsetting WHERE clientid = ?;
-- Area information
SELECT ca.* FROM clientarea ca WHERE ca.id = (SELECT clientareaid FROM client WHERE clientid = ?);
-- Soft delete (from deletetemp())
UPDATE client SET conditions = 1 WHERE clientid = ?;
-- Restore (from returndelete())
UPDATE client SET conditions = 0 WHERE clientid = ?;
SELECT cdc.*, u.employeename as username
FROM clientdebtchange cdc
LEFT JOIN user u ON u.userid = cdc.userid
WHERE cdc.clientid = ?
ORDER BY cdc.clientdebtchangedate DESC
LIMIT ? OFFSET ?;
-- Quick search (from queryallExtLimited())
SELECT clientname, clientid, clientmobile, clientdebt
FROM client
WHERE (clientname LIKE "%?%"
OR clientphone LIKE "?%"
OR clientmobile LIKE "?%"
OR clientcode LIKE "?%")
AND conditions = 0
ORDER BY clientdate DESC, clientid DESC
LIMIT 50;
-- Add essential indexes
CREATE INDEX idx_client_name ON client(clientname);
CREATE INDEX idx_client_phone ON client(clientphone);
CREATE INDEX idx_client_mobile ON client(clientmobile);
CREATE INDEX idx_client_area ON client(clientareaid);
CREATE INDEX idx_client_conditions ON client(conditions);
CREATE INDEX idx_client_debt ON client(clientdebt);
CREATE INDEX idx_client_adddate ON client(addDate);
CREATE INDEX idx_client_code ON client(clientcode);
-- Debt history indexes
CREATE INDEX idx_debtchange_client ON clientdebtchange(clientid);
CREATE INDEX idx_debtchange_date ON clientdebtchange(clientdebtchangedate);
CREATE INDEX idx_debtchange_type ON clientdebtchange(clientdebtchangetype);
-- Search optimization indexes
CREATE INDEX idx_client_search ON client(clientname, clientphone, clientmobile, clientcode);
CREATE INDEX idx_client_conditions_date ON client(conditions, clientdate);
CREATE INDEX idx_client_type ON client(typeclientid);
curl -X POST http://localhost/api/v1/clients \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-jwt-token" \
-d '{
"clientname": "John Doe",
"clientphone": "01234567890",
"clientmobile": "01234567891",
"clientaddress": "123 Main St",
"clientareaid": 1,
"clientdebt": 0.00,
"debtLimit": 5000.00,
"store_all": 1,
"client_type_ids": [1, 2]
}'
curl -X GET "http://localhost/api/v1/clients?area_id=1&conditions=0&page=1&limit=20" \
-H "Authorization: Bearer your-jwt-token"
curl -X PUT http://localhost/api/v1/clients/123 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-jwt-token" \
-d '{
"clientname": "John Doe Updated",
"clientdebt": 1500.00,
"conditions": 0
}'
class ClientAPI {
constructor(baseURL, token) {
this.baseURL = baseURL;
this.token = token;
}
async getClients(filters = {}) {
const params = new URLSearchParams(filters);
const response = ">await fetch(${this.baseURL}/api/v1/clients?${params}, {
headers: { 'Authorization039;: Bearer ${this.token} }
});
return await response.json();
}
async createClient(clientData) {
const response = ">await fetch(${this.baseURL}/api/v1/clients, {
method: 'POST039;,
headers: {
'Content-Type039;: 039;application/json039;,
'Authorization039;: Bearer ${this.token}
},
body: JSON.stringify(clientData)
});
return await response.json();
}
async updateClient(clientId, updateData) {
const response = ">await fetch(${this.baseURL}/api/v1/clients/${clientId}, {
method: 'PUT039;,
headers: {
'Content-Type039;: 039;application/json039;,
'Authorization039;: Bearer ${this.token}
},
body: JSON.stringify(updateData)
});
return await response.json();
}
async getDebtHistory(clientId, page = 1) {
const response = await fetch(
${this.baseURL}/api/v1/clients/${clientId}/debt-history?page=${page},
{
headers: { 'Authorization039;: Bearer ${this.token} }
}
);
return await response.json();
}
}
// Usage
">const clientAPI = new ClientAPI('http://localhost039;, 039;your-token039;);
">const clients = await clientAPI.getClients({ conditions: 0, limit: 50 });
/api/v1/, /api/v2/
// Caching strategy
Cache::remember("client_{">$clientId}", 3600, ">function() ">use ($clientId) {
return Client::with(['area039;, 039;types039;, 039;debtHistory039;])->find($clientId);
});
// Pagination optimization
$clients = Client::where('conditions039;, 0)
->with(['area039;, 039;types039;])
->orderBy('addDate039;, 039;desc039;)
->cursorPaginate(20);
// Search optimization with Elasticsearch
$results = Client::search($query)
->where('conditions039;, 0)
->take(50)
->get();
">public function test_can_create_client_with_full_profile()
{
$clientData = [
'clientname039; => 039;Test Client039;,
'clientphone039; => 039;01234567890039;,
'clientareaid039; => 1,
'clientdebt039; => 1000.00,
'store_all039; => 1
];
$response = ">$this->postJson('/api/v1/clients039;, $clientData);
$response->assertStatus(201)
->assertJson([
'success039; => true,
'data039; => [
'clientname039; => 039;Test Client039;
]
]);
$this->assertDatabaseHas('client039;, [039;clientname039; => 039;Test Client039;]);
$this->assertDatabaseHas('clientdebtchange039;, [
'clientid039; => $response[039;data039;][039;clientid039;],
'clientdebtchangeamount039; => 1000.00
]);
}
">public function test_cannot_create_duplicate_client_name()
{
Client::factory()->create(['clientname039; => 039;Existing Client039;]);
$response = $this->postJson('/api/v1/clients039;, [
'clientname039; => 039;Existing Client039;
]);
$response->assertStatus(409)
->assertJson([
'success039; => false,
'error039; => [
'code039; => 039;DUPLICATE_CLIENT_NAME039;
]
]);
}
---
// Line 332-337 in clientController.php
">elseif ($do == "addexcel") {
include_once("../public/authentication.php");
$smarty->display("clientview/uploadexcel.html");
}
API Endpoint: GET /controllers/clientController.php?do=addexcel
Business Logic:
// Line 338-345 in clientController.php
">elseif ($do == "addfromexcel") {
include_once("../public/authentication.php");
try {
addFromExcel(); // Complex function starting at line 946
header("location:?do=sucess");
} ">catch (Exception $e) {
header("location:?do=error");
}
}
SQL Operations from addFromExcel() function:
-- Process Excel file with PHPExcel library
-- Extract client data from Excel rows 4 to highest row
-- For each row, create client with accounting integration:
INSERT INTO client (
clientname, clientcode, clientaddress, clientphone, clientmobile,
clientdebt, debtLimit, clientdetails, clientareaid, conditions,
clientdate, userid, store_all, typeClient_all, priceTypeId,
treeId, dailyentryid
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?)
-- CRITICAL: Create Daily Entry (Journal Entry) for Each Client
INSERT INTO dailyentry (
totalcreditor, totaldebtor, thedate, userid, condition,
dDateTime, entryComment, fromFlag, branchid, operationId
) VALUES (?, ?, ?, ?, 0, ?, 'قيد يومية لإضافة عميل من ملف اكسل039;, 1, ?, ?)
-- Insert debit/credit entries if client has initial debt
INSERT INTO dailyentrydebtor (dailyentryid, dailyentrydebtoraccounttreeid, dailyentrydebtorammount)
VALUES (?, ?, ?)
INSERT INTO dailyentrycreditor (dailyentryid, dailyentrycreditoraccounttreeid, dailyentrycreditorammount)
VALUES (?, ?, ?)
-- Account Tree Integration
INSERT INTO accountstree (
accountstreename, accountstreecode, accountstreebalance,
accountstreetype, accountstreeconditions, accountstreedate,
parentaccountstreeId, userid, accountstreelevel, branchid
) VALUES (?, ?, ?, 57, 0, ?, ?, ?, 3, ?)
-- Tamween insurance details if present
INSERT INTO tamweenclientdetail (
clientid, noOfPersonsDa3m, noOfPersonsTamween, cardNum, cardPassword
) VALUES (?, ?, ?, ?, ?)
API Endpoint: POST /controllers/clientController.php?do=addfromexcel
Request: Multipart form with Excel file
Business Logic:
// Line 585-615 in clientController.php
">elseif ($do == "downloadphone") {
$groupData = $userGroupDAO->load($_SESSION['usergroupid039;]);
">if ($groupData->downloadClientPhones != 1) {
header("location:login.php?do=en");
exit();
}
">$clientData = $clientDAO->queryAll();
// Create phone.txt with all phones
$handle = fopen('../upload/phone.txt039;, 039;w+039;);
foreach (">$clientData as $data) {
">if ($data->clientmobile) fwrite(">$handle, $data->clientmobile . ',039;);
">if ($data->clientphone) fwrite(">$handle, $data->clientphone . ',039;);
}
// Create mobile.txt with mobile numbers only (starting with 01)
$mobFile = fopen('../upload/mobile.txt039;, 039;w+039;);
foreach (">$clientData as $data) {
if (strpos($data->clientmobile, '01039;) === 0)
fwrite(">$mobFile, $data->clientmobile . ',039;);
if (strpos($data->clientphone, '01039;) === 0)
fwrite(">$mobFile, $data->clientphone . ',039;);
}
}
SQL Operations:
-- Get all active clients with phone data
SELECT * FROM client WHERE conditions = 0
API Endpoint: GET /controllers/clientController.php?do=downloadphone
Business Logic:
/upload/ directory
// Line 506-514 in clientController.php
">elseif ($do == "editprint") {
include_once("../public/authentication.php");
">$loadData = edit(); // Calls edit() function
$smarty->assign("loadData", $loadData[0]);
$smarty->assign("tamweenClientDetail", $loadData[1]);
$smarty->display("clientview/editprint.html");
$smarty->assign("customPrint", 1);
}
Uses edit() function SQL operations:
-- Load client data
SELECT * FROM client WHERE clientid = ? AND conditions = 0
-- Load client area data
SELECT * FROM clientarea WHERE id = ?
-- Load government data
SELECT * FROM government WHERE governmentid = ?
-- Load Tamween insurance details
SELECT * FROM tamweenclientdetail WHERE clientid = ?
API Endpoint: GET /controllers/clientController.php?do=editprint&id={id}
Business Logic: Same as edit but with print styling
---
// Line 515-523 in clientController.php
">elseif ($do == "printdatail") {
include_once("../public/authentication.php");
">$clientid = $_GET["id"];
$client = R::getRow('SELECT * FROM client where clientid = ?039;, [$clientid]);
$smarty->assign("client", $client);
">$clientdetails = R::findOne('clientdetails039;, 039;clientid = ?039;, [$clientid]);
$smarty->assign("clientdetails", $clientdetails);
$smarty->display("clientview/printdetail.html");
$smarty->assign("customPrint", 1);
}
SQL Operations:
-- Get complete client data
SELECT * FROM client WHERE clientid = ?
-- Get extended client details
SELECT * FROM clientdetails WHERE clientid = ?
API Endpoint: GET /controllers/clientController.php?do=printdatail&id={id}
---
// Line 470-479 in clientController.php
">elseif ($do == "executeOperation") {
try {
executeOperation(); // Function at line 1311
show();
$smarty->display("clientview/show.html");
} ">catch (Exception $e) {
$smarty->display("error.html");
}
}
SQL Operations from executeOperation() function:
-- Bulk soft delete
UPDATE client SET conditions = 1 WHERE clientid IN (?, ?, ?)
-- Bulk restore
UPDATE client SET conditions = 0 WHERE clientid IN (?, ?, ?)
-- Operations based on $_POST['operation039;] parameter
-- operation = 'delete039; or operation = 039;restore039;
API Endpoint: POST /controllers/clientController.php?do=executeOperation
Request Body: Selected client IDs and operation type
---
// Line 616-620 in clientController.php
">elseif ($do == "sendEmailOtp") {
">$clientId = $_POST['id039;];
$results = generateAndSendEmailOtp($clientId); // Function at line 1709
$data = array('status039; => 039;success039;, 039;status_code039; => 200, 039;message039; => ">$httpStatusCodes[200], 039;data039; => $results);
">echo json_encode($data);
}
Complete generateAndSendEmailOtp() function implementation:
// Line 1709-1784 in clientController.php
function generateAndSendEmailOtp($clientId) {
global ">$httpStatusCodes, ">$clientDAO, $langs;
$otp = rand(1000, 9999); // Generate 4-digit OTP
$expiry = date('Y-m-d H:i:s039;, strtotime(039;+10 minutes039;));
">$client = ">$clientDAO->load($clientId);
">$email = $client->txtemail;
">if (!$email) {
// Return error if no email found
return error response
}
// Store OTP directly in client table
">$client->email_otp = $otp;
$client->email_otp_expiry = $expiry;
$client->email_verified = 0;
">$clientDAO->update($client);
// Send email using PHPMailer
$onlinestoremainsetting = R::load('onlinestoremainsetting039;, 1);
$templatePath = __DIR__ . '/../views/default/sendemailotp.html039;;
">$body = file_get_contents($templatePath);
$body = str_replace('{{ reset_link }}039;, ">$otp, $body);
// PHPMailer configuration and send
">$mail = new PHPMailer(true);
$mail->isSMTP();
">$mail->Host = $onlinestoremainsetting->mail_host;
// ... complete mail configuration
return success/error response with OTP
}
SQL Operations from generateAndSendEmailOtp() function:
-- Load client data and email
SELECT * FROM client WHERE clientid = ? AND conditions = 0
-- Load email settings
SELECT * FROM onlinestoremainsetting WHERE id = 1
-- Store OTP directly in client table (NOT separate table)
UPDATE client SET
email_otp = ?,
email_otp_expiry = ?,
email_verified = 0
WHERE clientid = ?
API Endpoint: POST /controllers/clientController.php?do=sendEmailOtp
Request Body: {"id": 123}
Response:
{
"status": "success",
"status_code": 200,
"message": "OK",
"data": {"otp": 1234} // OTP included in response for testing
}
Business Logic:
// Line 621-624 in clientController.php
">elseif ($do == "verifyEmailOtp") {
">$clientId = $_POST['id039;];
$enteredOtp = $_POST['otp039;];
$result = verifyEmailOtp(">$clientId, $enteredOtp); // Function at line 1787
}
Complete verifyEmailOtp() function implementation:
// Line 1787-1805 in clientController.php
function verifyEmailOtp(">$clientId, $enteredOtp) {
global ">$clientDAO, ">$httpStatusCodes, $langs;
">$client = ">$clientDAO->load($clientId);
">if (">$client->email_otp == $enteredOtp && strtotime($client->email_otp_expiry) > time()) {
// OTP is valid and not expired
$client->email_verified = 1;
$client->email_otp = null; // Clear OTP
$client->email_otp_expiry = null; // Clear expiry
">$clientDAO->update($client);
return array(
'status039; => 039;success039;,
'status_code039; => 200,
'data039; => [
'id039; => $clientId,
'name039; => $client->clientname,
'email039; => $client->txtemail,
'email_verified039; => $client->email_verified
]
);
} else {
// OTP invalid or expired
return error response
}
}
SQL Operations from verifyEmailOtp() function:
-- Load client with stored OTP
SELECT * FROM client WHERE clientid = ?
-- If OTP valid, clear it and mark as verified
UPDATE client SET
email_verified = 1,
email_otp = NULL,
email_otp_expiry = NULL
WHERE clientid = ?
API Endpoint: POST /controllers/clientController.php?do=verifyEmailOtp
Request Body: {"id": 123, "otp": "1234"}
Response Success:
{
"status": "success",
"status_code": 200,
"data": {
"id": 123,
"name": "Ahmed Mohamed",
"email": "ahmed@example.com",
"email_verified": 1
}
}
Response Error:
{
"status": "error",
"status_code": 400,
"errors": ["Invalid or expired OTP"],
"clientwebApiId": 0
}
Business Logic:
POST /controllers/clientController.php?do=verifyEmailOtp
---
// Line 625-627 in clientController.php
">elseif ($do == "sucess") {
$smarty->display("succes.html");
}
API Endpoint: GET /controllers/clientController.php?do=sucess
Business Logic: Simple success page display
---
// Line 628+ in clientController.php
">elseif ($do == "error") {
$smarty->display("error.html");
}
API Endpoint: GET /controllers/clientController.php?do=error
Business Logic: Simple error page display
---
// Line 440-469 in clientController.php
">elseif ($do == "showByType") {
include_once("../public/authentication.php");
$allclientarea = $ClientareaDAO->queryAll();
$smarty->assign("allclientarea", $allclientarea);
">$youtubes = $youtubeLinkDAO->queryAll();
$smarty->assign("youtubes", $youtubes);
showByType(); // Function at line 1240
$smarty->display("clientview/showByType.html");
}
SQL Operations from showByType() function:
-- Get all client types
SELECT * FROM typeclient WHERE conditions = 0 ORDER BY typeId
-- Get clients for each type
SELECT client.*, typeclient.typename
FROM client
JOIN typeclient ON client.typeclientid LIKE CONCAT('%,039;, typeclient.typeId, 039;,%039;)
WHERE client.conditions = 0
ORDER BY typeclient.typename, client.clientname
API Endpoint: GET /controllers/clientController.php?do=showByType
---