Skip to content

Commit 0743797

Browse files
committed
feat(phase-9): add Approvals, Subcontracting API controllers and API test files
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c0f7810 commit 0743797

5 files changed

Lines changed: 291 additions & 0 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use App\Modules\Approvals\Models\ApprovalRequest;
6+
use Illuminate\Http\JsonResponse;
7+
use Illuminate\Http\Request;
8+
9+
class ApprovalsApiController extends ApiController
10+
{
11+
public function index(Request $request): JsonResponse
12+
{
13+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
14+
15+
$query = ApprovalRequest::where('tenant_id', $tenantId);
16+
17+
if ($request->filled('status')) {
18+
$query->where('status', $request->status);
19+
}
20+
21+
if ($request->filled('requestor_id')) {
22+
$query->where('requested_by', $request->requestor_id);
23+
}
24+
25+
return $this->paginated($query->latest()->paginate(15));
26+
}
27+
28+
public function show(int $id): JsonResponse
29+
{
30+
$approvalRequest = ApprovalRequest::with([
31+
'requestedBy',
32+
'actions.actor',
33+
'workflow.steps.approver',
34+
])->findOrFail($id);
35+
36+
return $this->success($approvalRequest);
37+
}
38+
39+
public function store(Request $request): JsonResponse
40+
{
41+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
42+
43+
$validated = $request->validate([
44+
'workflow_id' => 'required|integer|exists:approval_workflows,id',
45+
'entity_type' => 'required|string|max:100',
46+
'entity_id' => 'required|integer',
47+
'entity_title' => 'required|string|max:255',
48+
]);
49+
50+
$validated['tenant_id'] = $tenantId;
51+
$validated['requested_by'] = $request->user()->id;
52+
$validated['status'] = 'pending';
53+
$validated['current_step'] = 1;
54+
55+
$approvalRequest = ApprovalRequest::create($validated);
56+
57+
return $this->success($approvalRequest->load('workflow'), 201);
58+
}
59+
60+
public function approve(Request $request, int $id): JsonResponse
61+
{
62+
$approvalRequest = ApprovalRequest::findOrFail($id);
63+
64+
$approvalRequest->update([
65+
'status' => 'approved',
66+
'approved_at' => now(),
67+
]);
68+
69+
return $this->success($approvalRequest->fresh());
70+
}
71+
72+
public function reject(Request $request, int $id): JsonResponse
73+
{
74+
$validated = $request->validate([
75+
'reason' => 'required|string',
76+
]);
77+
78+
$approvalRequest = ApprovalRequest::findOrFail($id);
79+
80+
$approvalRequest->update([
81+
'status' => 'rejected',
82+
'rejected_at' => now(),
83+
'rejection_reason' => $validated['reason'],
84+
]);
85+
86+
return $this->success($approvalRequest->fresh());
87+
}
88+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use App\Modules\Subcontracting\Models\SubcontractOrder;
6+
use App\Modules\Subcontracting\Models\SubcontractComponent;
7+
use Illuminate\Http\JsonResponse;
8+
use Illuminate\Http\Request;
9+
10+
class SubcontractingApiController extends ApiController
11+
{
12+
/**
13+
* GET /api/v1/subcontracting/orders
14+
*/
15+
public function index(Request $request): JsonResponse
16+
{
17+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
18+
19+
$query = SubcontractOrder::where('tenant_id', $tenantId);
20+
21+
if ($status = $request->query('status')) {
22+
$query->where('status', $status);
23+
}
24+
25+
if ($vendorId = $request->query('vendor_id')) {
26+
$query->where('vendor_id', $vendorId);
27+
}
28+
29+
$paginator = $query->latest()->paginate(20);
30+
31+
return $this->paginated($paginator);
32+
}
33+
34+
/**
35+
* GET /api/v1/subcontracting/orders/{id}
36+
*/
37+
public function show(int $id): JsonResponse
38+
{
39+
$order = SubcontractOrder::with(['components'])->findOrFail($id);
40+
41+
return $this->success($order);
42+
}
43+
44+
/**
45+
* POST /api/v1/subcontracting/orders
46+
*/
47+
public function store(Request $request): JsonResponse
48+
{
49+
$validated = $request->validate([
50+
'vendor_id' => 'required|integer',
51+
'reference' => 'nullable|string|max:100',
52+
'finished_product' => 'required|string|max:255',
53+
'finished_qty' => 'required|numeric|min:0',
54+
'unit_price' => 'nullable|numeric|min:0',
55+
'notes' => 'nullable|string',
56+
'components' => 'nullable|array',
57+
'components.*.component_name' => 'required_with:components|string|max:255',
58+
'components.*.quantity' => 'required_with:components|numeric|min:0',
59+
'components.*.unit' => 'nullable|string|max:50',
60+
]);
61+
62+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
63+
64+
$components = $validated['components'] ?? [];
65+
unset($validated['components']);
66+
67+
$validated['tenant_id'] = $tenantId;
68+
$validated['status'] = 'draft';
69+
70+
$order = SubcontractOrder::create($validated);
71+
72+
foreach ($components as $component) {
73+
SubcontractComponent::create([
74+
'tenant_id' => $tenantId,
75+
'subcontract_id' => $order->id,
76+
'component_name' => $component['component_name'],
77+
'quantity' => $component['quantity'],
78+
'unit' => $component['unit'] ?? null,
79+
]);
80+
}
81+
82+
return $this->success($order->load('components'), 201);
83+
}
84+
85+
/**
86+
* PUT /api/v1/subcontracting/orders/{id}
87+
*/
88+
public function update(Request $request, int $id): JsonResponse
89+
{
90+
$order = SubcontractOrder::findOrFail($id);
91+
92+
$validated = $request->validate([
93+
'vendor_id' => 'sometimes|integer',
94+
'reference' => 'nullable|string|max:100',
95+
'finished_product' => 'sometimes|string|max:255',
96+
'finished_qty' => 'sometimes|numeric|min:0',
97+
'unit_price' => 'nullable|numeric|min:0',
98+
'notes' => 'nullable|string',
99+
'status' => 'nullable|string|in:draft,sent,in_progress,done,cancelled',
100+
'sent_at' => 'nullable|date',
101+
'received_at' => 'nullable|date',
102+
]);
103+
104+
$order->update($validated);
105+
106+
return $this->success($order);
107+
}
108+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
use App\Models\User;
4+
use App\Modules\Core\Models\Tenant;
5+
use App\Modules\KnowledgeBase\Models\KbArticle;
6+
use App\Modules\KnowledgeBase\Models\KbCategory;
7+
use Database\Seeders\RolePermissionSeeder;
8+
9+
beforeEach(function () {
10+
$this->seed(RolePermissionSeeder::class);
11+
$this->tenant = Tenant::create(['name' => 'KB Co', 'slug' => 'kb-co']);
12+
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
13+
$this->user->assignRole('super-admin');
14+
$this->token = $this->user->createToken('test')->plainTextToken;
15+
app()->instance('tenant', $this->tenant);
16+
});
17+
18+
it('returns articles for authenticated user', function () {
19+
KbArticle::create([
20+
'tenant_id' => $this->tenant->id,
21+
'title' => 'Test Article',
22+
'content' => 'Article content here',
23+
'status' => 'published',
24+
'author_id' => $this->user->id,
25+
]);
26+
27+
$response = $this->withToken($this->token)->getJson('/api/v1/knowledge-base/articles');
28+
29+
$response->assertStatus(200)
30+
->assertJsonStructure(['success', 'data', 'meta']);
31+
});
32+
33+
it('requires authentication for articles', function () {
34+
$this->getJson('/api/v1/knowledge-base/articles')->assertStatus(401);
35+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use App\Models\User;
4+
use App\Modules\Core\Models\Tenant;
5+
use App\Modules\LiveChat\Models\ChatChannel;
6+
use App\Modules\LiveChat\Models\ChatSession;
7+
use Database\Seeders\RolePermissionSeeder;
8+
9+
beforeEach(function () {
10+
$this->seed(RolePermissionSeeder::class);
11+
$this->tenant = Tenant::create(['name' => 'Chat Co', 'slug' => 'chat-co']);
12+
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
13+
$this->user->assignRole('super-admin');
14+
$this->token = $this->user->createToken('test')->plainTextToken;
15+
app()->instance('tenant', $this->tenant);
16+
});
17+
18+
it('returns channels for authenticated user', function () {
19+
ChatChannel::create([
20+
'tenant_id' => $this->tenant->id,
21+
'name' => 'Support Channel',
22+
]);
23+
24+
$response = $this->withToken($this->token)->getJson('/api/v1/live-chat/channels');
25+
26+
$response->assertStatus(200)
27+
->assertJsonStructure(['success', 'data', 'meta']);
28+
});
29+
30+
it('requires authentication for channels', function () {
31+
$this->getJson('/api/v1/live-chat/channels')->assertStatus(401);
32+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use App\Models\User;
4+
use App\Modules\Core\Models\Tenant;
5+
6+
it('returns campaigns for authenticated user', function () {
7+
$tenant = Tenant::factory()->create();
8+
$user = User::factory()->create(['tenant_id' => $tenant->id]);
9+
$token = $user->createToken('test')->plainTextToken;
10+
$response = $this->withToken($token)->getJson('/api/v1/marketing/campaigns');
11+
$response->assertStatus(200)->assertJsonStructure(['success', 'data', 'meta']);
12+
});
13+
14+
it('requires authentication for campaigns', function () {
15+
$this->getJson('/api/v1/marketing/campaigns')->assertStatus(401);
16+
});
17+
18+
it('returns mailing lists for authenticated user', function () {
19+
$tenant = Tenant::factory()->create();
20+
$user = User::factory()->create(['tenant_id' => $tenant->id]);
21+
$token = $user->createToken('test')->plainTextToken;
22+
$response = $this->withToken($token)->getJson('/api/v1/marketing/mailing-lists');
23+
$response->assertStatus(200)->assertJsonStructure(['success', 'data', 'meta']);
24+
});
25+
26+
it('requires authentication for mailing lists', function () {
27+
$this->getJson('/api/v1/marketing/mailing-lists')->assertStatus(401);
28+
});

0 commit comments

Comments
 (0)