ljoaquim 2 viikkoa sitten
vanhempi
sitoutus
d0f856af2d

+ 3 - 0
.env.example

@@ -1,6 +1,9 @@
 APP_PORT=8080
 JWT_SECRET=Aer8woa9zeec2gai4ahQuah3Ahbee5eiSefae8pheepahnootuShoo0oKahf
 
+#================= CONFIG =================
+ROOT_DIR=
+
 #================= DATABASE (PostgreSQL) =================
 DB_HOST=
 DB_PORT=

+ 3 - 2
controllers/CompanyWithUserController.php

@@ -38,7 +38,8 @@ class CompanyWithUserController
                 ->key('country', val::stringType()->notEmpty()->length(1, 100))
                 ->key('kyc', val::intType())
                 ->key('birthdate', val::intType())
-                ->key('cpf', val::stringType()->notEmpty()->length(1, 50))
+                ->key('cpf', val::stringType()->notEmpty()->length(1, 11))
+                ->key('cnpj', val::stringType()->notEmpty()->length(1, 14))
                 ->assert($body);
         } catch (ValidationException $e) {
             return ResponseLib::sendFail("Validation failed: " . $e->getFullMessage(), [], "E_VALIDATE")->withStatus(400);
@@ -48,7 +49,7 @@ class CompanyWithUserController
             $pdo = $GLOBALS['pdo'];
             $pdo->beginTransaction();
 
-            $companyId = $this->companyModel->createCompany($body['company_name'], 'a');
+            $companyId = $this->companyModel->createCompany($body['company_name'], $body['cnpj'], 'a');
             $roleId = 1;
             $chk = $pdo->prepare('SELECT 1 FROM "role" WHERE role_id = :rid');
             $chk->execute(['rid' => $roleId]);

+ 53 - 0
controllers/RegisterCprController.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace Controllers;
+
+use Libs\ResponseLib;
+use Models\CprModel;
+use Models\StatusModel;
+use Psr\Http\Message\ServerRequestInterface;
+use Respect\Validation\Exceptions\ValidationException;
+use Respect\Validation\Validator as val;
+
+class RegisterCprController
+{
+    private CprModel $cprModel;
+    private StatusModel $statusModel;
+
+    public function __construct()
+    {
+        $this->cprModel = new CprModel();
+        $this->statusModel = new StatusModel();
+    }
+
+    public function __invoke(ServerRequestInterface $request)
+    {
+        $body = json_decode((string)$request->getBody(), true) ?? [];
+
+        try {
+            val::key('cpr_children_codes', val::arrayType()->notEmpty()->each(val::stringType()->notEmpty()))
+                ->assert($body);
+        } catch (ValidationException $e) {
+            return ResponseLib::sendFail(
+                'Validation failed: ' . $e->getFullMessage(),
+                [],
+                'E_VALIDATE'
+            )->withStatus(400);
+        }
+
+        $statusId = $this->statusModel->getIdByStatus('pending');
+        if ($statusId === null) {
+            return ResponseLib::sendFail('Pending status not found', [], 'E_DATABASE')->withStatus(500);
+        }
+
+        try {
+            $record = $this->cprModel->create($body, $statusId);
+        } catch (\InvalidArgumentException $e) {
+            return ResponseLib::sendFail($e->getMessage(), [], 'E_VALIDATE')->withStatus(400);
+        } catch (\Throwable $e) {
+            return ResponseLib::sendFail('Failed to create CPR: ' . $e->getMessage(), [], 'E_DATABASE')->withStatus(500);
+        }
+
+        return ResponseLib::sendOk($record, 'S_CREATED');
+    }
+}

+ 5 - 2
migrations/migrations_v1.sql

@@ -7,7 +7,8 @@ CREATE TABLE "chain" (
 CREATE TABLE "company" (
     "company_id" SERIAL PRIMARY KEY,
     "company_name" TEXT NOT NULL,
-    "company_flag" TEXT NOT NULL
+    "company_flag" TEXT NOT NULL,
+    "company_cnpj" TEXT NOT NULL
 );
 
 CREATE TABLE "role" (
@@ -145,7 +146,9 @@ CREATE TABLE "cpr" (
     "cpr_ibge_code" TEXT NOT NULL,
     "cpr_guarantee_limit_type_code" TEXT NOT NULL,
     "cpr_mother_code" TEXT NOT NULL,
-    "cpr_children_codes" JSONB NOT NULL  -- Armazena o array como JSONB, já que é uma lista
+    "cpr_children_codes" TEXT NOT NULL, -- text array
+    "status_id" INTEGER NOT NULL,
+    FOREIGN KEY ("status_id") REFERENCES "status" ("status_id")
 );
 
 CREATE TABLE "commodities" (

+ 5 - 2
public/index.php

@@ -38,12 +38,12 @@ $authJwt = new JwtAuthMiddleware();
 $app->get('/jwthelloworld', $authJwt,\Controllers\HelloController::class);
 
 $app->post('/login', \Controllers\LoginController::class);
-$app->post('/register', \Controllers\RegisterController::class);
+$app->post('/register', $authJwt, \Controllers\RegisterController::class);
 $app->post('/user/get', $authJwt, \Controllers\UserGetController::class);
 $app->post('/user/delete', $authJwt, \Controllers\UserDeleteController::class);
 
 // Public endpoint to create company, user, and wallet in a single transaction
-$app->post('/companyWithUser/create', \Controllers\CompanyWithUserController::class);
+$app->post('/company/user/create', \Controllers\CompanyWithUserController::class);
 
 // Authenticated user profile updates
 $app->post('/user/change-email', $authJwt, \Controllers\UserChangeEmailController::class);
@@ -55,4 +55,7 @@ $app->post('/commodity/update', $authJwt, \Controllers\CommodityUpdateController
 $app->post('/commodity/delete', $authJwt, \Controllers\CommodityDeleteController::class);
 $app->get('/commodities', $authJwt, \Controllers\CommoditiesGetController::class);
 
+// CPR registration
+$app->post('/cpr/create', $authJwt, \Controllers\RegisterCprController::class);
+
 $app->run();

+ 156 - 0
test/cpr_create.sh

@@ -0,0 +1,156 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+BASE_URL="${BASE_URL:-http://localhost:8000}"
+JWT_TOKEN="${JWT_TOKEN:-}"
+PAYLOAD_FILE="${1:-}"
+
+usage() {
+    cat <<'USAGE'
+Usage: JWT_TOKEN=<token> ./cpr_create.sh <payload.json>
+
+Environment variables:
+  BASE_URL   Base URL of the API (default: http://localhost:8000)
+  JWT_TOKEN  Bearer token used for Authorization header (required)
+
+Arguments:
+  <payload.json>  Optional path to the JSON payload body. If omitted, a template is used.
+USAGE
+}
+
+if [[ -z "$JWT_TOKEN" ]]; then
+    echo "[error] JWT_TOKEN environment variable must be set" >&2
+    usage
+    exit 1
+fi
+
+API_ENDPOINT="$BASE_URL/cpr/create"
+
+if [[ -n "$PAYLOAD_FILE" ]]; then
+    if [[ ! -f "$PAYLOAD_FILE" ]]; then
+        echo "[error] Payload file '$PAYLOAD_FILE' not found" >&2
+        exit 1
+    fi
+    PAYLOAD_CONTENT=$(<"$PAYLOAD_FILE")
+else
+    read -r -d '' PAYLOAD_CONTENT <<'JSON' || true
+{
+  "cpr_type_code": "TEMPLATE",
+  "cpr_otc_register_account_code": "REG-001",
+  "cpr_otc_payment_agent_account_code": "PAY-001",
+  "cpr_otc_custodian_account_code": "CUS-001",
+  "cpr_internal_control_number": "ICN-0001",
+  "cpr_electronic_emission_indicator": "Y",
+  "cpr_isin_code": "BR1234567890",
+  "cpr_issue_date": "2024-01-01",
+  "cpr_maturity_date": "2025-01-01",
+  "cpr_issue_quantity": "1000",
+  "cpr_issue_value": "500000",
+  "cpr_issue_financial_value": "500000",
+  "cpr_unit_value": "500",
+  "cpr_reference_date": "2024-01-01",
+  "cpr_profitability_start_date": "2024-02-01",
+  "cpr_automatic_expiration_indicator": "N",
+  "cpr_collateral_type_code": "COLL",
+  "cpr_collateral_type_name": "Collateral",
+  "cpr_constitution_process_indicator": "MANUAL",
+  "cpr_otc_bondsman_account_code": "BOND-001",
+  "cpr_document_number": "DOC123456",
+  "cpr_product_name": "Soy",
+  "cpr_product_class_name": "Grain",
+  "cpr_product_harvest": "2024",
+  "cpr_product_description": "Template product description",
+  "cpr_product_quantity": "100",
+  "cpr_measure_unit_name": "TON",
+  "cpr_packaging_way_name": "Bulk",
+  "cpr_product_status_code": "READY",
+  "cpr_production_type_code": "AGR",
+  "cpr_issuer_name": "Issuer Name",
+  "cpr_person_type_acronym": "PJ",
+  "cpr_state_acronym": "SP",
+  "cpr_city_name": "São Paulo",
+  "cpr_ibge_code": "3550308",
+  "cpr_issuer_legal_nature_code": "2046",
+  "cpr_otc_favored_account_code": "FAV-001",
+  "cpr_self_number": "SELF-001",
+  "cpr_settlement_modality_type_code": "CASH",
+  "cpr_otc_settlement_bank_account_code": "SET-001",
+  "cpr_deposit_quantity": "100",
+  "cpr_deposit_unit_price_value": "5000",
+  "cpr_payment_method_code": "PIX",
+  "cpr_index_code": "CDI",
+  "cpr_index_short_name": "CDI",
+  "cpr_vcp_indicator_type_code": "VAR",
+  "cpr_indexador_percentage_value": "10",
+  "cpr_interest_rate_spread_percentage": "2",
+  "cpr_interest_rate_criteria_type_code": "SIMPLE",
+  "cpr_interest_payment_date": "2024-07-01",
+  "cpr_interest_payment_value": "10000",
+  "cpr_interest_payment_frequency_code": "ANNUAL",
+  "cpr_interest_months_quantity": "12",
+  "cpr_time_unit_type_code": "MONTH",
+  "cpr_deadline_type_code": "FIXED",
+  "cpr_payment_start_date": "2024-02-01",
+  "cpr_amortization_type_code": "LINEAR",
+  "cpr_amortization_months_quantity": "12",
+  "cpr_amortization_start_date": "2024-03-01",
+  "cpr_scr_type_code": "SCR",
+  "cpr_scr_customer_detail": "Customer detail",
+  "cpr_contract_code": "CON-001",
+  "cpr_operation_modality_type_code": "CRED",
+  "cpr_bacen_reference_code": "12345",
+  "cpr_finality_code": "FIN",
+  "cpr_ipoc_code": "IPO-001",
+  "cpr_calculation_type_code": "SIMPLE",
+  "cpr_initial_exchange_value": "10000",
+  "cpr_fixing_type_code": "FIXED",
+  "cpr_data_source_type_code": "MANUAL",
+  "cpr_adjustment_frequency_type_code": "ANNUAL",
+  "cpr_adjustment_pro_rata_type_code": "DAY",
+  "cpr_adjustment_type_code": "FIXED",
+  "cpr_creditor_name": "Creditor",
+  "cpr_ballast_type_code": "BAL",
+  "cpr_lot_number": "LOT-001",
+  "cpr_ballast_quantity": "100",
+  "cpr_currency_code": "BRL",
+  "cpr_transaction_identification": "TRX-001",
+  "cpr_additional_text": "Additional notes",
+  "cpr_number": "CPR-001",
+  "cpr_contract_number": "CONT-001",
+  "cpr_event_type_code": "EVT",
+  "cpr_event_original_date": "2024-01-01",
+  "cpr_unit_price_value": "5000",
+  "cpr_interest_unit_price_value": "100",
+  "cpr_residual_value": "10000",
+  "cpr_amortization_percentage": "10",
+  "cpr_event_quantity": "1",
+  "cpr_production_place_name": "Farm",
+  "cpr_property_registration_number": "REG-123",
+  "cpr_notary_name": "Notary",
+  "cpr_total_production_area_in_hectares_number": "100",
+  "cpr_total_area_in_hectares_number": "120",
+  "cpr_car_code": "CAR-123",
+  "cpr_latitude_code": "-23.5505",
+  "cpr_longitude_code": "-46.6333",
+  "cpr_zip_code": "01000-000",
+  "cpr_green_cpr_indicator": "N",
+  "cpr_green_cpr_certificate_name": "Certificate",
+  "cpr_green_cpr_certificate_cnpj_number": "12.345.678/0001-99",
+  "cpr_green_cpr_georeferencing_description": "Geo desc",
+  "cpr_green_cpr_declaration_indicator": "N",
+  "cpr_document_deadline_days_number": "30",
+  "cpr_place_name": "São Paulo",
+  "cpr_guarantee_limit_type_code": "GLT",
+  "cpr_mother_code": "MOM-001",
+  "cpr_children_codes": ["CHD-001", "CHD-002"]
+}
+JSON
+fi
+
+echo "[info] Sending CPR payload '${PAYLOAD_FILE:-template}' to $API_ENDPOINT"
+
+curl --fail --show-error --silent \
+    -X POST "$API_ENDPOINT" \
+    -H "Content-Type: application/json" \
+    -H "Authorization: Bearer $JWT_TOKEN" \
+    --data "$PAYLOAD_CONTENT" | jq .

+ 41 - 0
test/fetch_jwt.sh

@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+BASE_URL="${BASE_URL:-http://localhost:8000}"
+LOGIN_ENDPOINT="$BASE_URL/login"
+EMAIL="${LOGIN_EMAIL:-tester@example.com}"
+PASSWORD="${LOGIN_PASSWORD:-Password123!}"
+
+usage() {
+    cat <<'USAGE'
+Usage: LOGIN_EMAIL=<email> LOGIN_PASSWORD=<password> ./fetch_jwt.sh
+
+Environment variables:
+  BASE_URL       Base URL of the API (default: http://localhost:8000)
+  LOGIN_EMAIL    Email used for authentication (default: tester@example.com)
+  LOGIN_PASSWORD Password used for authentication (default: Password123!)
+USAGE
+}
+
+if [[ -z "$EMAIL" || -z "$PASSWORD" ]]; then
+    echo "[error] LOGIN_EMAIL and LOGIN_PASSWORD must be provided" >&2
+    usage
+    exit 1
+fi
+
+PAYLOAD=$(jq -n --arg email "$EMAIL" --arg password "$PASSWORD" '{email: $email, password: $password}')
+
+RESPONSE=$(curl --fail --show-error --silent \
+    -X POST "$LOGIN_ENDPOINT" \
+    -H "Content-Type: application/json" \
+    --data "$PAYLOAD")
+
+TOKEN=$(echo "$RESPONSE" | jq -r '.data.token // empty')
+
+if [[ -z "$TOKEN" ]]; then
+    echo "[error] Unable to extract token from response" >&2
+    echo "$RESPONSE" >&2
+    exit 1
+fi
+
+echo "$TOKEN"