feat: allowed more than one assigned person
This commit is contained in:
parent
983097ea7c
commit
83c0f449f5
@ -71,12 +71,10 @@ public function handle()
|
|||||||
$salesUser = User::where('name', $this->rowValue($row, 'sales'))->first();
|
$salesUser = User::where('name', $this->rowValue($row, 'sales'))->first();
|
||||||
$pic = User::where('name', $this->rowValue($row, 'pic'))->first();
|
$pic = User::where('name', $this->rowValue($row, 'pic'))->first();
|
||||||
if ($pic) {
|
if ($pic) {
|
||||||
ClientUserAssignation::updateOrCreate(
|
ClientUserAssignation::firstOrCreate(
|
||||||
[
|
[
|
||||||
'client_id' => $client->id,
|
'client_id' => $client->id,
|
||||||
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
||||||
],
|
|
||||||
[
|
|
||||||
'user_id' => $pic->id,
|
'user_id' => $pic->id,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@ -345,12 +345,10 @@ private function linkInvoiceClient(ClientInvoice $invoice, array $validated): Cl
|
|||||||
$client = Client::findOrFail($validated['client_id']);
|
$client = Client::findOrFail($validated['client_id']);
|
||||||
abort_unless($this->hierarchyService->canViewClient(Auth::user(), $client), 403);
|
abort_unless($this->hierarchyService->canViewClient(Auth::user(), $client), 403);
|
||||||
|
|
||||||
ClientUserAssignation::updateOrCreate(
|
ClientUserAssignation::firstOrCreate(
|
||||||
[
|
[
|
||||||
'client_id' => $client->id,
|
'client_id' => $client->id,
|
||||||
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
||||||
],
|
|
||||||
[
|
|
||||||
'user_id' => Auth::id(),
|
'user_id' => Auth::id(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@ -621,12 +619,10 @@ public function storeClient(Request $request, ClientInvoice $invoice)
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
ClientUserAssignation::updateOrCreate(
|
ClientUserAssignation::firstOrCreate(
|
||||||
[
|
[
|
||||||
'client_id' => $client->id,
|
'client_id' => $client->id,
|
||||||
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
||||||
],
|
|
||||||
[
|
|
||||||
'user_id' => Auth::id(),
|
'user_id' => Auth::id(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@ -56,11 +56,17 @@ public function accounts()
|
|||||||
)
|
)
|
||||||
->get();
|
->get();
|
||||||
$customerMap = $localClients->map(function ($data) {
|
$customerMap = $localClients->map(function ($data) {
|
||||||
$assignedPerson = $data->assignations->firstWhere('role', ClientUserAssignation::ROLE_ASSIGNED_PERSON);
|
$assignedPeople = $data->assignations
|
||||||
|
->where('role', ClientUserAssignation::ROLE_ASSIGNED_PERSON)
|
||||||
|
->map(fn (ClientUserAssignation $assignation) => $assignation->user?->name)
|
||||||
|
->filter()
|
||||||
|
->values();
|
||||||
$salesPerson = $data->assignations->firstWhere('role', ClientUserAssignation::ROLE_SALES_PERSON);
|
$salesPerson = $data->assignations->firstWhere('role', ClientUserAssignation::ROLE_SALES_PERSON);
|
||||||
$data['industry'] = $data->industry;
|
$data['industry'] = $data->industry;
|
||||||
$data['sql_acc_code'] = implode(',', $data->customers->pluck('sql_acc_code')->toArray());
|
$data['sql_acc_code'] = implode(',', $data->customers->pluck('sql_acc_code')->toArray());
|
||||||
$data['assigned_person'] = $assignedPerson?->user?->name;
|
$data['assigned_person'] = $assignedPeople->isNotEmpty()
|
||||||
|
? $assignedPeople->implode(', ')
|
||||||
|
: null;
|
||||||
$data['sales_person'] = $salesPerson?->user?->name;
|
$data['sales_person'] = $salesPerson?->user?->name;
|
||||||
$data['latest_remaining_amount'] = $data->latestRemainingAmount();
|
$data['latest_remaining_amount'] = $data->latestRemainingAmount();
|
||||||
|
|
||||||
@ -174,7 +180,8 @@ public function updateAccount(Request $request, $id)
|
|||||||
'customer_id' => ['required', 'string', 'unique:clients,customer_id,'.$localClient->id],
|
'customer_id' => ['required', 'string', 'unique:clients,customer_id,'.$localClient->id],
|
||||||
'industry' => ['nullable', 'string'],
|
'industry' => ['nullable', 'string'],
|
||||||
'sql_acc_code' => ['nullable', 'string'],
|
'sql_acc_code' => ['nullable', 'string'],
|
||||||
'assigned_person' => ['nullable', 'integer', 'exists:users,id'],
|
'assigned_person' => ['nullable', 'array'],
|
||||||
|
'assigned_person.*' => ['integer', 'exists:users,id'],
|
||||||
'sales_person' => ['nullable', 'integer', 'exists:users,id'],
|
'sales_person' => ['nullable', 'integer', 'exists:users,id'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -200,29 +207,60 @@ public function updateAccount(Request $request, $id)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$assignmentValues = [
|
$this->syncAssignedPeople($localClient, $validated['assigned_person'] ?? []);
|
||||||
ClientUserAssignation::ROLE_ASSIGNED_PERSON => $validated['assigned_person'] ?? null,
|
$this->syncSingleAssignment(
|
||||||
ClientUserAssignation::ROLE_SALES_PERSON => $validated['sales_person'] ?? null,
|
$localClient,
|
||||||
];
|
ClientUserAssignation::ROLE_SALES_PERSON,
|
||||||
|
$validated['sales_person'] ?? null,
|
||||||
foreach ($assignmentValues as $role => $userId) {
|
);
|
||||||
if ($userId === null) {
|
|
||||||
$localClient->assignations()->where('role', $role)->delete();
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$localClient->assignations()->updateOrCreate(
|
|
||||||
['role' => $role],
|
|
||||||
['user_id' => $userId]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
->route('google-ads.accounts.edit', ['id' => $localClient->customer_id])
|
->route('google-ads.accounts.edit', ['id' => $localClient->customer_id])
|
||||||
->with('message-info', 'Account details updated.');
|
->with('message-info', 'Account details updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function syncAssignedPeople(Client $client, array $userIds): void
|
||||||
|
{
|
||||||
|
$userIds = collect($userIds)
|
||||||
|
->filter(fn ($userId) => is_numeric($userId))
|
||||||
|
->map(fn ($userId) => (int) $userId)
|
||||||
|
->unique()
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$query = $client->assignations()
|
||||||
|
->where('role', ClientUserAssignation::ROLE_ASSIGNED_PERSON);
|
||||||
|
|
||||||
|
if ($userIds === []) {
|
||||||
|
$query->delete();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->whereNotIn('user_id', $userIds)->delete();
|
||||||
|
|
||||||
|
foreach ($userIds as $userId) {
|
||||||
|
$client->assignations()->firstOrCreate([
|
||||||
|
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
||||||
|
'user_id' => $userId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function syncSingleAssignment(Client $client, int $role, ?int $userId): void
|
||||||
|
{
|
||||||
|
$client->assignations()->where('role', $role)->delete();
|
||||||
|
|
||||||
|
if ($userId === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client->assignations()->create([
|
||||||
|
'role' => $role,
|
||||||
|
'user_id' => $userId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function campaigns($id)
|
public function campaigns($id)
|
||||||
{
|
{
|
||||||
$campaigns = $this->adsService->listCampaigns($id);
|
$campaigns = $this->adsService->listCampaigns($id);
|
||||||
@ -249,11 +287,15 @@ private function hydrateClient(array $account): array
|
|||||||
);
|
);
|
||||||
// dd($localClient);
|
// dd($localClient);
|
||||||
$localClient->load(['assignations.user', 'invoices.payments.items.billingItemType','customers']);
|
$localClient->load(['assignations.user', 'invoices.payments.items.billingItemType','customers']);
|
||||||
$assignments = $localClient->assignations
|
$assignments = [
|
||||||
->mapWithKeys(function (ClientUserAssignation $assignation) {
|
ClientUserAssignation::ROLE_ASSIGNED_PERSON => $localClient->assignations
|
||||||
return [$assignation->role => $assignation->user_id];
|
->where('role', ClientUserAssignation::ROLE_ASSIGNED_PERSON)
|
||||||
})
|
->pluck('user_id')
|
||||||
->toArray();
|
->values()
|
||||||
|
->all(),
|
||||||
|
ClientUserAssignation::ROLE_SALES_PERSON => $localClient->assignations
|
||||||
|
->firstWhere('role', ClientUserAssignation::ROLE_SALES_PERSON)?->user_id,
|
||||||
|
];
|
||||||
|
|
||||||
$users = User::orderBy('name')
|
$users = User::orderBy('name')
|
||||||
->get(['id', 'name', 'email'])
|
->get(['id', 'name', 'email'])
|
||||||
@ -420,12 +462,10 @@ public function insertCSVDataToDB()
|
|||||||
$salesUser = User::where('name', $row['sales'])->first();
|
$salesUser = User::where('name', $row['sales'])->first();
|
||||||
$pic = User::where('name', $row['pic'])->first();
|
$pic = User::where('name', $row['pic'])->first();
|
||||||
if ($pic) {
|
if ($pic) {
|
||||||
ClientUserAssignation::updateOrCreate(
|
ClientUserAssignation::firstOrCreate(
|
||||||
[
|
[
|
||||||
'client_id' => $client->id,
|
'client_id' => $client->id,
|
||||||
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
'role' => ClientUserAssignation::ROLE_ASSIGNED_PERSON,
|
||||||
],
|
|
||||||
[
|
|
||||||
'user_id' => $pic->id,
|
'user_id' => $pic->id,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@ -25,11 +25,13 @@ public static function roles(): array
|
|||||||
'id' => self::ROLE_ASSIGNED_PERSON,
|
'id' => self::ROLE_ASSIGNED_PERSON,
|
||||||
'label' => 'Assigned Person',
|
'label' => 'Assigned Person',
|
||||||
'field' => 'assigned_person',
|
'field' => 'assigned_person',
|
||||||
|
'multiple' => true,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'id' => self::ROLE_SALES_PERSON,
|
'id' => self::ROLE_SALES_PERSON,
|
||||||
'label' => 'Sales Person',
|
'label' => 'Sales Person',
|
||||||
'field' => 'sales_person',
|
'field' => 'sales_person',
|
||||||
|
'multiple' => false,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Select,
|
Select,
|
||||||
|
MultiSelect,
|
||||||
Title,
|
Title,
|
||||||
Divider,
|
Divider,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
@ -23,6 +24,7 @@ interface AssignmentRole {
|
|||||||
id: number;
|
id: number;
|
||||||
label: string;
|
label: string;
|
||||||
field: "assigned_person" | "sales_person";
|
field: "assigned_person" | "sales_person";
|
||||||
|
multiple?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectOption {
|
interface SelectOption {
|
||||||
@ -35,7 +37,7 @@ interface AccountFormData {
|
|||||||
customer_id: string;
|
customer_id: string;
|
||||||
industry: string;
|
industry: string;
|
||||||
sql_acc_code: string;
|
sql_acc_code: string;
|
||||||
assigned_person: string;
|
assigned_person: string[];
|
||||||
sales_person: string;
|
sales_person: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ interface Props {
|
|||||||
id: string;
|
id: string;
|
||||||
client: Client;
|
client: Client;
|
||||||
clientAssignmentRoles: AssignmentRole[];
|
clientAssignmentRoles: AssignmentRole[];
|
||||||
clientAssignments: Record<number, number | null>;
|
clientAssignments: Record<number, number | number[] | null>;
|
||||||
assignmentUsers: SelectOption[];
|
assignmentUsers: SelectOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,13 +61,24 @@ export default function Page({
|
|||||||
customer_id: client.customer_id ?? id,
|
customer_id: client.customer_id ?? id,
|
||||||
industry: client.industry ?? "",
|
industry: client.industry ?? "",
|
||||||
sql_acc_code: client.sql_acc_code ?? "",
|
sql_acc_code: client.sql_acc_code ?? "",
|
||||||
assigned_person: "",
|
assigned_person: [],
|
||||||
sales_person: "",
|
sales_person: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
clientAssignmentRoles.forEach((role) => {
|
clientAssignmentRoles.forEach((role) => {
|
||||||
const value = clientAssignments?.[role.id];
|
const value = clientAssignments?.[role.id];
|
||||||
initialData[role.field] = value ? String(value) : "";
|
|
||||||
|
if (role.field === "assigned_person") {
|
||||||
|
initialData.assigned_person = Array.isArray(value)
|
||||||
|
? value.map(String)
|
||||||
|
: value
|
||||||
|
? [String(value)]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialData.sales_person = value ? String(value) : "";
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<AccountFormData>(initialData);
|
const form = useForm<AccountFormData>(initialData);
|
||||||
@ -77,7 +90,9 @@ export default function Page({
|
|||||||
...data,
|
...data,
|
||||||
industry: data.industry || null,
|
industry: data.industry || null,
|
||||||
sql_acc_code: data.sql_acc_code || null,
|
sql_acc_code: data.sql_acc_code || null,
|
||||||
assigned_person: parseInt(data.assigned_person) || null,
|
assigned_person: data.assigned_person
|
||||||
|
.map((userId) => parseInt(userId))
|
||||||
|
.filter(Boolean),
|
||||||
sales_person: parseInt(data.sales_person) || null,
|
sales_person: parseInt(data.sales_person) || null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -169,19 +184,33 @@ export default function Page({
|
|||||||
breakpoints={[{ maxWidth: "md", cols: 1 }]}
|
breakpoints={[{ maxWidth: "md", cols: 1 }]}
|
||||||
spacing="md"
|
spacing="md"
|
||||||
>
|
>
|
||||||
{clientAssignmentRoles.map((role) => (
|
{clientAssignmentRoles.map((role) =>
|
||||||
<Select
|
role.multiple || role.field === "assigned_person" ? (
|
||||||
key={role.id}
|
<MultiSelect
|
||||||
label={role.label}
|
key={role.id}
|
||||||
placeholder={`Select ${role.label}`}
|
label={role.label}
|
||||||
data={assignmentUsers}
|
placeholder={`Select ${role.label}`}
|
||||||
value={form.data[role.field]}
|
data={assignmentUsers}
|
||||||
onChange={(value) => form.setData(role.field, value ?? "")}
|
value={form.data.assigned_person}
|
||||||
error={form.errors[role.field]}
|
onChange={(value) => form.setData("assigned_person", value)}
|
||||||
clearable
|
error={form.errors.assigned_person}
|
||||||
searchable
|
clearable
|
||||||
/>
|
searchable
|
||||||
))}
|
/>
|
||||||
|
) : (
|
||||||
|
<Select
|
||||||
|
key={role.id}
|
||||||
|
label={role.label}
|
||||||
|
placeholder={`Select ${role.label}`}
|
||||||
|
data={assignmentUsers}
|
||||||
|
value={form.data.sales_person}
|
||||||
|
onChange={(value) => form.setData("sales_person", value ?? "")}
|
||||||
|
error={form.errors.sales_person}
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@ -132,20 +132,20 @@ export default function TicketDetails({
|
|||||||
return <Badge color={getStatusColor(value)}>{value}</Badge>;
|
return <Badge color={getStatusColor(value)}>{value}</Badge>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
accessorKey: 'latest_remaining_amount',
|
// accessorKey: 'latest_remaining_amount',
|
||||||
header: 'Remaining Amount',
|
// header: 'Remaining Amount',
|
||||||
size: 180,
|
// size: 180,
|
||||||
Cell: ({ cell }: any) => {
|
// Cell: ({ cell }: any) => {
|
||||||
const amount = parseAmount(cell.getValue());
|
// const amount = parseAmount(cell.getValue());
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<Text color={amount < 0 ? 'red' : 'green'} weight={700}>
|
// <Text color={amount < 0 ? 'red' : 'green'} weight={700}>
|
||||||
{currencyFormatter.format(amount)}
|
// {currencyFormatter.format(amount)}
|
||||||
</Text>
|
// </Text>
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
accessorKey: 'latest_start_date',
|
accessorKey: 'latest_start_date',
|
||||||
header: 'Latest Start Date',
|
header: 'Latest Start Date',
|
||||||
|
|||||||
@ -85,7 +85,7 @@ interface Props {
|
|||||||
can_modify?: boolean;
|
can_modify?: boolean;
|
||||||
};
|
};
|
||||||
clientAssignmentRoles: AssignmentRole[];
|
clientAssignmentRoles: AssignmentRole[];
|
||||||
clientAssignments: Record<number, number | null>;
|
clientAssignments: Record<number, number | number[] | null>;
|
||||||
assignmentUsers: SelectOption[];
|
assignmentUsers: SelectOption[];
|
||||||
clientInvoices: ClientInvoice[];
|
clientInvoices: ClientInvoice[];
|
||||||
clientAdjustments: ClientInvoiceAdjustment[];
|
clientAdjustments: ClientInvoiceAdjustment[];
|
||||||
@ -97,6 +97,7 @@ interface AssignmentRole {
|
|||||||
id: number;
|
id: number;
|
||||||
label: string;
|
label: string;
|
||||||
field: string;
|
field: string;
|
||||||
|
multiple?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectOption {
|
interface SelectOption {
|
||||||
@ -611,15 +612,22 @@ export default function TicketDetails({
|
|||||||
}, [activities]);
|
}, [activities]);
|
||||||
|
|
||||||
const lookupAssignmentLabel = (roleId: number) => {
|
const lookupAssignmentLabel = (roleId: number) => {
|
||||||
const userId = clientAssignments?.[roleId];
|
const userIds = clientAssignments?.[roleId];
|
||||||
if (!userId) {
|
if (!userIds || (Array.isArray(userIds) && userIds.length === 0)) {
|
||||||
return '—';
|
return '—';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const ids = Array.isArray(userIds) ? userIds : [userIds];
|
||||||
assignmentUsers.find((option) => option.value === String(userId))
|
const labels = ids
|
||||||
?.label ?? '—'
|
.map(
|
||||||
);
|
(userId) =>
|
||||||
|
assignmentUsers.find(
|
||||||
|
(option) => option.value === String(userId),
|
||||||
|
)?.label,
|
||||||
|
)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
return labels.length > 0 ? labels.join(', ') : '—';
|
||||||
};
|
};
|
||||||
|
|
||||||
const accountSummary = [
|
const accountSummary = [
|
||||||
|
|||||||
4
resources/js/types/index.d.ts
vendored
4
resources/js/types/index.d.ts
vendored
@ -62,8 +62,8 @@ export interface Client {
|
|||||||
latest_start_date?: string | null;
|
latest_start_date?: string | null;
|
||||||
latest_end_date?: string | null;
|
latest_end_date?: string | null;
|
||||||
latest_remaining_days?: number | null;
|
latest_remaining_days?: number | null;
|
||||||
assigned_person?: string;
|
assigned_person?: string | null;
|
||||||
sales_person?: string;
|
sales_person?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Role {
|
export interface Role {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user