## Linear Ticket: https://linear.app/chatwoot/issue/CW-6081/review-feedback ## Description Assignment V2 Service Enhancements - Enable Assignment V2 on plan upgrade - Fix UI issue with fair distribution policy display - Add advanced assignment feature flag and enhance Assignment V2 capabilities ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? This has been tested using the UI. ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes auto-assignment execution paths, rate limiting defaults, and feature-flag gating (including premium plan behavior), which could affect which conversations get assigned and when. UI rewires inbox settings and policy flows, so regressions are possible around navigation/linking and feature visibility. > > **Overview** > **Adds a new premium `advanced_assignment` feature flag** and uses it to gate capacity/balanced assignment features in the UI (sidebar entry, settings routes, assignment-policy landing cards) and backend (Enterprise balanced selector + capacity filtering). `advanced_assignment` is marked premium, included in Business plan entitlements, and auto-synced in Enterprise accounts when `assignment_v2` is toggled. > > **Improves Assignment V2 policy UX** by adding an inbox-level “Conversation Assignment” section (behind `assignment_v2`) that can link/unlink an assignment policy, navigate to create/edit policy flows with `inboxId` query context, and show an inbox-link prompt after creating a policy. The policy form now defaults to enabled, disables the `balanced` option with a premium badge/message when unavailable, and inbox lists support click-to-navigate. > > **Tightens/adjusts auto-assignment behavior**: bulk assignment now requires `inbox.enable_auto_assignment?`, conversation ordering uses the attached `assignment_policy` priority, and rate limiting uses `assignment_policy` config with an infinite default limit while still tracking assignments. Tests and i18n strings are updated accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 23bc03bf75ee4376071e4d7fc7cd564c601d33d7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Pranav <pranav@chatwoot.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
101 lines
2.8 KiB
Vue
101 lines
2.8 KiB
Vue
<script setup>
|
|
import { ref, computed, onMounted } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import Input from 'dashboard/components-next/input/Input.vue';
|
|
import DurationInput from 'dashboard/components-next/input/DurationInput.vue';
|
|
import { DURATION_UNITS } from 'dashboard/components-next/input/constants';
|
|
|
|
const { t } = useI18n();
|
|
|
|
const fairDistributionLimit = defineModel('fairDistributionLimit', {
|
|
type: Number,
|
|
default: 100,
|
|
set(value) {
|
|
return Number(value) || 0;
|
|
},
|
|
});
|
|
|
|
// The model value is in seconds (for the backend/DB)
|
|
// DurationInput works in minutes internally
|
|
// We need to convert between seconds and minutes
|
|
const fairDistributionWindow = defineModel('fairDistributionWindow', {
|
|
type: Number,
|
|
default: 3600,
|
|
set(value) {
|
|
return Number(value) || 0;
|
|
},
|
|
});
|
|
|
|
const windowUnit = ref(DURATION_UNITS.MINUTES);
|
|
|
|
// Convert seconds to minutes for DurationInput
|
|
const windowInMinutes = computed({
|
|
get() {
|
|
return Math.floor((fairDistributionWindow.value || 0) / 60);
|
|
},
|
|
set(minutes) {
|
|
fairDistributionWindow.value = minutes * 60;
|
|
},
|
|
});
|
|
|
|
// Detect unit based on minutes (converted from seconds)
|
|
const detectUnit = minutes => {
|
|
const m = Number(minutes) || 0;
|
|
if (m === 0) return DURATION_UNITS.MINUTES;
|
|
if (m % (24 * 60) === 0) return DURATION_UNITS.DAYS;
|
|
if (m % 60 === 0) return DURATION_UNITS.HOURS;
|
|
return DURATION_UNITS.MINUTES;
|
|
};
|
|
|
|
onMounted(() => {
|
|
windowUnit.value = detectUnit(windowInMinutes.value);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="flex items-start xl:items-center flex-col md:flex-row gap-4 lg:gap-3 bg-n-solid-1 p-4 outline outline-1 outline-n-weak rounded-xl"
|
|
>
|
|
<div class="flex items-center gap-3">
|
|
<label class="text-sm font-medium text-n-slate-12">
|
|
{{
|
|
t(
|
|
'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.FORM.FAIR_DISTRIBUTION.INPUT_MAX'
|
|
)
|
|
}}
|
|
</label>
|
|
<div class="flex-1">
|
|
<Input
|
|
v-model="fairDistributionLimit"
|
|
type="number"
|
|
placeholder="100"
|
|
max="100000"
|
|
class="w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex sm:flex-row flex-col items-start sm:items-center gap-4">
|
|
<label class="text-sm font-medium text-n-slate-12">
|
|
{{
|
|
t(
|
|
'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.FORM.FAIR_DISTRIBUTION.DURATION'
|
|
)
|
|
}}
|
|
</label>
|
|
|
|
<div
|
|
class="flex items-center gap-2 flex-1 [&>select]:!bg-n-alpha-2 [&>select]:!outline-none [&>select]:hover:brightness-110"
|
|
>
|
|
<!-- allow 10 mins to 999 days (in minutes) -->
|
|
<DurationInput
|
|
v-model:model-value="windowInMinutes"
|
|
v-model:unit="windowUnit"
|
|
:min="10"
|
|
:max="1438560"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|