feat: колонка has_delivery_problem + delivery_problem_note

- saveShipmentData автоматически заполняет has_delivery_problem (boolean) и delivery_problem_note (текст с перечислением неотгруженных позиций и причин)
- OrdersTable: колонка 'Проблема' показывает ⚠ Проблема + детали из delivery_problem_note
- orderGroupViews: warning-тон для групп с hasDeliveryProblem
- n8n/1C: просто читать has_delivery_problem (0/1) и delivery_problem_note
This commit is contained in:
root 2026-06-15 17:32:14 +00:00
parent 187ef90c96
commit b888eba09b
3 changed files with 37 additions and 50 deletions

View File

@ -136,29 +136,17 @@ export const OrdersTable = ({
<div className="mt-3 text-sm text-[var(--color-text-muted)]">{buildGroupSummary(group)}</div>
<div className="mt-2 text-sm text-[var(--color-text-muted)]">{renderMobileOrderNumbers(group)}</div>
{(() => {
const issues = getShipmentIssues(group);
const data = group?.driverShipmentData;
if (!Array.isArray(data) || data.length === 0) return null;
const shippedCount = data.filter((i) => i.shipped).length;
const total = data.length;
if (issues && issues.length > 0) {
return (
{group.hasDeliveryProblem && (
<div className="mt-2 rounded-lg border border-[var(--color-warning)] bg-[var(--color-warning-soft)] px-2 py-1 text-xs">
<span className="font-medium text-[var(--color-warning)]"> Отгрузка: {shippedCount}/{total}</span>
<span className="text-[var(--color-text-muted)]"> {issues.slice(0, 2).map((i) => i.name).join(", ")}{issues.length > 2 ? ` +${issues.length - 2}` : ""}</span>
{issues.some((i) => i.comment) && (
<div className="mt-0.5 text-[var(--color-text-muted)]">{issues.filter((i) => i.comment).slice(0, 1).map((i) => i.comment)}</div>
<span className="font-medium text-[var(--color-warning)]"> Проблема с доставкой</span>
{group.deliveryProblemNote && (
<div className="mt-0.5 text-[var(--color-text-muted)]">{group.deliveryProblemNote}</div>
)}
</div>
);
}
return (
<div className="mt-2 text-xs text-[var(--color-accent)]">
Отгружено: {total}/{total}
</div>
);
})()}
)}
{!group.hasDeliveryProblem && Array.isArray(group?.driverShipmentData) && group.driverShipmentData.length > 0 && (
<div className="mt-2 text-xs text-[var(--color-accent)]"> Доставка без проблем</div>
)}
<div className="mt-3 text-xs text-[var(--color-text-muted)]">
{formatDateTime(group.updatedAt)}
</div>
@ -179,7 +167,7 @@ export const OrdersTable = ({
<div className="px-5 py-4 font-medium">Статус</div>
<div className="px-5 py-4 font-medium">Водитель</div>
<div className="px-5 py-4 font-medium">Дата доставки</div>
<div className="px-5 py-4 font-medium">Отгрузка</div>
<div className="px-5 py-4 font-medium">Проблема</div>
<div className="px-5 py-4 font-medium">Обновлён</div>
</div>
{orderGroups.map((group) => (
@ -213,26 +201,20 @@ export const OrdersTable = ({
)}
</div>
<div className="px-5 py-4 text-sm">
{(() => {
const issues = getShipmentIssues(group);
const data = group?.driverShipmentData;
if (!Array.isArray(data) || data.length === 0) {
return <span className="text-[var(--color-text-muted)]"></span>;
}
const shippedCount = data.filter((i) => i.shipped).length;
const total = data.length;
if (issues && issues.length > 0) {
return (
{group.hasDeliveryProblem ? (
<div>
<span className="text-[var(--color-warning)] font-medium"> {shippedCount}/{total}</span>
<div className="mt-0.5 text-xs text-[var(--color-text-muted)]">
{issues.slice(0, 2).map((i) => i.name).join(", ")}{issues.length > 2 ? ` +${issues.length - 2}` : ""}
<span className="text-[var(--color-warning)] font-medium"> Проблема</span>
{group.deliveryProblemNote && (
<div className="mt-0.5 text-xs text-[var(--color-text-muted)] line-clamp-2">
{group.deliveryProblemNote}
</div>
)}
</div>
);
}
return <span className="text-[var(--color-accent)]"> {total}/{total}</span>;
})()}
) : Array.isArray(group?.driverShipmentData) && group.driverShipmentData.length > 0 ? (
<span className="text-[var(--color-accent)]"> Ок</span>
) : (
<span className="text-[var(--color-text-muted)]"></span>
)}
</div>
<div className="px-5 py-4 text-sm text-[var(--color-text-muted)]">
{formatDateTime(group.updatedAt)}

View File

@ -461,12 +461,8 @@ export const buildOrderGroupBuckets = (groups) => {
export const getOrderGroupStatusTone = (group) => {
const deliveryStatus = group?.deliveryStatus || group?.delivery_status;
// Highlight groups with shipment issues (partial delivery)
const shipmentData = group?.driverShipmentData;
if (Array.isArray(shipmentData) && shipmentData.length > 0) {
const hasIssues = shipmentData.some((i) => !i.shipped);
if (hasIssues) return "warning";
}
// Highlight groups with delivery problems
if (group?.hasDeliveryProblem) return "warning";
if (deliveryStatus && deliveryStatus !== "pending_confirmation") {
return getOrderGroupDeliveryStatusTone(deliveryStatus);

View File

@ -221,6 +221,8 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
pickupDate: row.pickup_date || null,
pickupTimeSlot: row.pickup_time_slot || null,
driverShipmentData: row.driver_shipment_data || null,
hasDeliveryProblem: row.has_delivery_problem || false,
deliveryProblemNote: row.delivery_problem_note || null,
syncedTo1cAt: row.synced_to_1c_at || null,
deliveryHalfDay: getOrderGroupDeliveryHalfDay({
deliveryHalfDay: rawDeliveryHalfDay,
@ -262,7 +264,7 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
};
};
const ORDER_GROUP_SELECT_FIELDS = `id, group_key, order_numbers, status, delivery_status, sms_sent_at, created_at, updated_at, created_from_exchange_at, source_key, customer_name, customer_phone, customer_phone_normalized, customer_date, orders_total, orders_ready, orders_not_ready, source_orders, order_list, order_list_structured, delivery_invitation_id, delivery_link, notification_status, sms_attempts, first_sms_sent_at, second_sms_sent_at, last_sms_error, next_notification_check_at, delivery_date, delivery_time, delivery_address, customer_address, delivery_date_source, manual_confirmation_at, paid_storage_at, assigned_driver_id, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name), driver_shipment_data, delivery_type, pickup_date, pickup_time_slot, synced_to_1c_at`;
const ORDER_GROUP_SELECT_FIELDS = `id, group_key, order_numbers, status, delivery_status, sms_sent_at, created_at, updated_at, created_from_exchange_at, source_key, customer_name, customer_phone, customer_phone_normalized, customer_date, orders_total, orders_ready, orders_not_ready, source_orders, order_list, order_list_structured, delivery_invitation_id, delivery_link, notification_status, sms_attempts, first_sms_sent_at, second_sms_sent_at, last_sms_error, next_notification_check_at, delivery_date, delivery_time, delivery_address, customer_address, delivery_date_source, manual_confirmation_at, paid_storage_at, assigned_driver_id, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name), driver_shipment_data, delivery_type, pickup_date, pickup_time_slot, has_delivery_problem, delivery_problem_note, synced_to_1c_at`;
export const updateOrderGroupDeliveryChoice = async ({
orderGroupId,
@ -391,11 +393,18 @@ export const assignDriverToOrderGroup = async ({
export const saveShipmentData = async ({ orderGroupId, shipmentData }) => {
return safeSupabaseCall(async () => {
const client = requireSupabase();
const unshipped = shipmentData.filter((i) => !i.shipped);
const hasProblem = unshipped.length > 0;
const problemNote = hasProblem
? unshipped.map((i) => `${i.name}${i.quantity ? ` (${i.quantity}${i.unit ? ` ${i.unit}` : ""})` : ""}${i.comment ? `${i.comment}` : ""}`).join("; ")
: null;
const { error: updateError } = await client
.from("order_groups")
.update({
driver_shipment_data: shipmentData,
has_delivery_problem: hasProblem,
delivery_problem_note: problemNote,
updated_at: new Date().toISOString(),
})
.eq("id", orderGroupId);