[feat] negodata: 협력사 총매출액(total_revenue) 추가 + 우선선정(priority) 제거
Helped-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3176ec4262
commit
1ac42ddc9c
@ -124,7 +124,7 @@ class suppliers(MainTableMixin, MAIN_BASE):
|
||||
manager_name = Column(String(50), nullable=True)
|
||||
manager_email = Column(String(255), nullable=True)
|
||||
manager_contact_number = Column(String(20), nullable=True) # ERD 오타(manger) 교정
|
||||
priority = Column(String(10), nullable=True) # True/False 가 아닌 string value 가능
|
||||
total_revenue = Column(BigInteger, nullable=True) # 총매출액(원)
|
||||
|
||||
|
||||
class nego_cards(MainTableMixin, MAIN_BASE):
|
||||
|
||||
@ -14,7 +14,7 @@ from common.utils.gtime import GTime
|
||||
# 협력사 CRUD. 모든 조회/변경은 company_id 로 스코프된다(멀티테넌트).
|
||||
class ISupplierCRUD(ABC):
|
||||
@abstractmethod
|
||||
async def search(self, cdb: AsyncSession, company_id, search, priority, skip, limit) -> Tuple[ErrorType, list, int]:
|
||||
async def search(self, cdb: AsyncSession, company_id, search, skip, limit) -> Tuple[ErrorType, list, int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@ -44,7 +44,7 @@ class ISupplierCRUD(ABC):
|
||||
|
||||
class SupplierCRUD(ISupplierCRUD):
|
||||
async def search(
|
||||
self, cdb: AsyncSession, company_id, search: Optional[str], priority: Optional[str], skip: int, limit: int
|
||||
self, cdb: AsyncSession, company_id, search: Optional[str], skip: int, limit: int
|
||||
) -> Tuple[ErrorType, list, int]:
|
||||
try:
|
||||
conditions = [suppliers.deleted == False, suppliers.company_id == company_id] # noqa: E712
|
||||
@ -56,8 +56,6 @@ class SupplierCRUD(ISupplierCRUD):
|
||||
suppliers.manager_name.ilike(f"%{search}%"),
|
||||
)
|
||||
)
|
||||
if priority:
|
||||
conditions.append(suppliers.priority == priority)
|
||||
where = and_(*conditions)
|
||||
|
||||
cnt_err, cnt_rows = await DB_SESSION_MNG.execute(cdb, select(func.count()).select_from(suppliers).where(where))
|
||||
|
||||
@ -17,7 +17,7 @@ class Req_CreateSupplier(SupplierProtocol):
|
||||
manager_name: Optional[str] = None
|
||||
manager_email: Optional[str] = None
|
||||
manager_contact_number: Optional[str] = None
|
||||
priority: Optional[str] = None
|
||||
total_revenue: Optional[int] = None # 총매출액(원)
|
||||
|
||||
|
||||
class Req_UpdateSupplier(SupplierProtocol):
|
||||
@ -26,7 +26,7 @@ class Req_UpdateSupplier(SupplierProtocol):
|
||||
manager_name: Optional[str] = None
|
||||
manager_email: Optional[str] = None
|
||||
manager_contact_number: Optional[str] = None
|
||||
priority: Optional[str] = None
|
||||
total_revenue: Optional[int] = None
|
||||
|
||||
|
||||
class SupplierData(WebPacketProtocol):
|
||||
@ -40,7 +40,7 @@ class SupplierData(WebPacketProtocol):
|
||||
manager_name: Optional[str] = None
|
||||
manager_email: Optional[str] = None
|
||||
manager_contact_number: Optional[str] = None
|
||||
priority: Optional[str] = None
|
||||
total_revenue: Optional[int] = None # 총매출액(원)
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
|
||||
@ -24,10 +24,9 @@ async def list_suppliers(
|
||||
service: SupplierService = Depends(),
|
||||
user_info: UserInfo = Depends(IsValidAccessToken),
|
||||
search: str | None = Query(None, description="협력사명/코드/담당자명 검색"),
|
||||
priority: str | None = Query(None, description="우선순위 필터(HIGH/MEDIUM/LOW)"),
|
||||
pg: PageParams = Depends(),
|
||||
):
|
||||
return RemoveNoneResponse(await service.list_suppliers(user_info.company_id, search, priority, pg))
|
||||
return RemoveNoneResponse(await service.list_suppliers(user_info.company_id, search, pg))
|
||||
|
||||
|
||||
@router.post(path="/create", response_model=Res_Supplier, summary="협력사 등록")
|
||||
|
||||
@ -38,14 +38,14 @@ class SupplierService:
|
||||
return ErrorType.SUPPLIER_NOT_FOUND, None
|
||||
return ErrorType.SUCCESS, supplier
|
||||
|
||||
async def list_suppliers(self, company_id: str, search, priority, pg: PageParams) -> Res_SupplierList:
|
||||
async def list_suppliers(self, company_id: str, search, pg: PageParams) -> Res_SupplierList:
|
||||
res = Res_SupplierList(page=pg.page, size=pg.size)
|
||||
company_uuid = uuid.UUID(company_id)
|
||||
|
||||
err_type, rows, total = await DB_SESSION_MNG.execute_lambda(
|
||||
suppliers.DBType(),
|
||||
DBWRType.DB_READ.value,
|
||||
lambda s: self.supplier_crud.search(s, company_uuid, search, priority, pg.skip, pg.size),
|
||||
lambda s: self.supplier_crud.search(s, company_uuid, search, pg.skip, pg.size),
|
||||
)
|
||||
if err_type != ErrorType.SUCCESS:
|
||||
res.result.SetResult(err_type)
|
||||
@ -105,7 +105,7 @@ class SupplierService:
|
||||
manager_name=req.manager_name,
|
||||
manager_email=req.manager_email,
|
||||
manager_contact_number=req.manager_contact_number,
|
||||
priority=req.priority,
|
||||
total_revenue=req.total_revenue,
|
||||
)
|
||||
err_type = await DB_SESSION_MNG.execute_lambda_run(
|
||||
[suppliers.DBType()],
|
||||
|
||||
@ -163,7 +163,7 @@ export * from './reqCreateSupplierCode';
|
||||
export * from './reqCreateSupplierManagerContactNumber';
|
||||
export * from './reqCreateSupplierManagerEmail';
|
||||
export * from './reqCreateSupplierManagerName';
|
||||
export * from './reqCreateSupplierPriority';
|
||||
export * from './reqCreateSupplierTotalRevenue';
|
||||
export * from './reqLogin';
|
||||
export * from './reqRegenerateQuotation';
|
||||
export * from './reqUpdateCard';
|
||||
@ -216,7 +216,7 @@ export * from './reqUpdateSupplierManagerContactNumber';
|
||||
export * from './reqUpdateSupplierManagerEmail';
|
||||
export * from './reqUpdateSupplierManagerName';
|
||||
export * from './reqUpdateSupplierName';
|
||||
export * from './reqUpdateSupplierPriority';
|
||||
export * from './reqUpdateSupplierTotalRevenue';
|
||||
export * from './resCard';
|
||||
export * from './resCardCard';
|
||||
export * from './resCardList';
|
||||
@ -339,7 +339,7 @@ export * from './supplierDataCreatedAt';
|
||||
export * from './supplierDataManagerContactNumber';
|
||||
export * from './supplierDataManagerEmail';
|
||||
export * from './supplierDataManagerName';
|
||||
export * from './supplierDataPriority';
|
||||
export * from './supplierDataTotalRevenue';
|
||||
export * from './supplierDataUpdatedAt';
|
||||
export * from './supplierType';
|
||||
export * from './targetCandidate';
|
||||
|
||||
@ -10,10 +10,6 @@ export type ListSuppliersParams = {
|
||||
* 협력사명/코드/담당자명 검색
|
||||
*/
|
||||
search?: string | null;
|
||||
/**
|
||||
* 우선순위 필터(HIGH/MEDIUM/LOW)
|
||||
*/
|
||||
priority?: string | null;
|
||||
/**
|
||||
* @minimum 1
|
||||
*/
|
||||
|
||||
@ -8,7 +8,7 @@ import type { ReqCreateSupplierCode } from './reqCreateSupplierCode';
|
||||
import type { ReqCreateSupplierManagerName } from './reqCreateSupplierManagerName';
|
||||
import type { ReqCreateSupplierManagerEmail } from './reqCreateSupplierManagerEmail';
|
||||
import type { ReqCreateSupplierManagerContactNumber } from './reqCreateSupplierManagerContactNumber';
|
||||
import type { ReqCreateSupplierPriority } from './reqCreateSupplierPriority';
|
||||
import type { ReqCreateSupplierTotalRevenue } from './reqCreateSupplierTotalRevenue';
|
||||
|
||||
export interface ReqCreateSupplier {
|
||||
name?: string;
|
||||
@ -16,5 +16,5 @@ export interface ReqCreateSupplier {
|
||||
manager_name?: ReqCreateSupplierManagerName;
|
||||
manager_email?: ReqCreateSupplierManagerEmail;
|
||||
manager_contact_number?: ReqCreateSupplierManagerContactNumber;
|
||||
priority?: ReqCreateSupplierPriority;
|
||||
total_revenue?: ReqCreateSupplierTotalRevenue;
|
||||
}
|
||||
|
||||
@ -5,4 +5,4 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqCreateSupplierPriority = string | null;
|
||||
export type ReqCreateSupplierTotalRevenue = number | null;
|
||||
@ -9,7 +9,7 @@ import type { ReqUpdateSupplierCode } from './reqUpdateSupplierCode';
|
||||
import type { ReqUpdateSupplierManagerName } from './reqUpdateSupplierManagerName';
|
||||
import type { ReqUpdateSupplierManagerEmail } from './reqUpdateSupplierManagerEmail';
|
||||
import type { ReqUpdateSupplierManagerContactNumber } from './reqUpdateSupplierManagerContactNumber';
|
||||
import type { ReqUpdateSupplierPriority } from './reqUpdateSupplierPriority';
|
||||
import type { ReqUpdateSupplierTotalRevenue } from './reqUpdateSupplierTotalRevenue';
|
||||
|
||||
export interface ReqUpdateSupplier {
|
||||
name?: ReqUpdateSupplierName;
|
||||
@ -17,5 +17,5 @@ export interface ReqUpdateSupplier {
|
||||
manager_name?: ReqUpdateSupplierManagerName;
|
||||
manager_email?: ReqUpdateSupplierManagerEmail;
|
||||
manager_contact_number?: ReqUpdateSupplierManagerContactNumber;
|
||||
priority?: ReqUpdateSupplierPriority;
|
||||
total_revenue?: ReqUpdateSupplierTotalRevenue;
|
||||
}
|
||||
|
||||
@ -5,4 +5,4 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ReqUpdateSupplierPriority = string | null;
|
||||
export type ReqUpdateSupplierTotalRevenue = number | null;
|
||||
@ -8,7 +8,7 @@ import type { SupplierDataCode } from './supplierDataCode';
|
||||
import type { SupplierDataManagerName } from './supplierDataManagerName';
|
||||
import type { SupplierDataManagerEmail } from './supplierDataManagerEmail';
|
||||
import type { SupplierDataManagerContactNumber } from './supplierDataManagerContactNumber';
|
||||
import type { SupplierDataPriority } from './supplierDataPriority';
|
||||
import type { SupplierDataTotalRevenue } from './supplierDataTotalRevenue';
|
||||
import type { SupplierDataCreatedAt } from './supplierDataCreatedAt';
|
||||
import type { SupplierDataUpdatedAt } from './supplierDataUpdatedAt';
|
||||
|
||||
@ -21,7 +21,7 @@ export interface SupplierData {
|
||||
manager_name?: SupplierDataManagerName;
|
||||
manager_email?: SupplierDataManagerEmail;
|
||||
manager_contact_number?: SupplierDataManagerContactNumber;
|
||||
priority?: SupplierDataPriority;
|
||||
total_revenue?: SupplierDataTotalRevenue;
|
||||
created_at?: SupplierDataCreatedAt;
|
||||
updated_at?: SupplierDataUpdatedAt;
|
||||
}
|
||||
|
||||
@ -5,4 +5,4 @@
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type SupplierDataPriority = string | null;
|
||||
export type SupplierDataTotalRevenue = number | null;
|
||||
@ -17,13 +17,13 @@ type RawRow = {
|
||||
code: string;
|
||||
managerName: string;
|
||||
managerEmail: string;
|
||||
priority: string;
|
||||
totalRevenue: string;
|
||||
};
|
||||
|
||||
type ValidatedRow = RawRow & { status: '정상' | '오류'; message: string };
|
||||
|
||||
// 업로드 양식 한 줄(예시 행)
|
||||
type TemplateRow = { name: string; code: string; managerName: string; managerEmail: string; priority: string };
|
||||
type TemplateRow = { name: string; code: string; managerName: string; managerEmail: string; totalRevenue: string };
|
||||
|
||||
type ExcelUploadModalProps = {
|
||||
open: boolean;
|
||||
@ -68,7 +68,7 @@ function toSupplierCreate(row: RawRow): SupplierCreate {
|
||||
manager_name: row.managerName,
|
||||
manager_email: row.managerEmail,
|
||||
manager_contact_number: '010-0000-0000',
|
||||
priority: row.priority,
|
||||
total_revenue: row.totalRevenue?.trim() ? Number(row.totalRevenue.replace(/[^0-9]/g, '')) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -81,9 +81,9 @@ export function downloadPartnerTemplate() {
|
||||
{ header: '식별코드', value: (r) => r.code },
|
||||
{ header: '담당자명', value: (r) => r.managerName },
|
||||
{ header: '담당자이메일', value: (r) => r.managerEmail },
|
||||
{ header: '우선순위', value: (r) => r.priority },
|
||||
{ header: '총매출액', value: (r) => r.totalRevenue },
|
||||
],
|
||||
[{ name: '예시) (주)한빛정밀', code: 'PART-EXAMPLE-001', managerName: '김철수 과장', managerEmail: 'cs.kim@example.com', priority: 'HIGH' }],
|
||||
[{ name: '예시) (주)한빛정밀', code: 'PART-EXAMPLE-001', managerName: '김철수 과장', managerEmail: 'cs.kim@example.com', totalRevenue: '5000000000' }],
|
||||
);
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ export function ExcelUploadModal({ open, partners, onConfirm, onClose }: ExcelUp
|
||||
code: r['식별코드'] ?? '',
|
||||
managerName: r['담당자명'] ?? '',
|
||||
managerEmail: r['담당자이메일'] ?? '',
|
||||
priority: r['우선순위'] ?? 'MEDIUM',
|
||||
totalRevenue: r['총매출액'] ?? '',
|
||||
}));
|
||||
setExcelFile(file.name);
|
||||
setRows(loaded);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
@ -9,8 +9,7 @@ import { Typography } from '@/components/ui/typography';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Sheet } from '@/components/ui/sheet';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { type Partner, priorityOptions } from '../types';
|
||||
import { type Partner } from '../types';
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().trim().min(1, '회사명/협력사명을 작성해 주십시오.'),
|
||||
@ -18,7 +17,7 @@ const schema = z.object({
|
||||
managerName: z.string().trim().min(1, '담당자명을 입력해 주십시오.'),
|
||||
managerEmail: z.string().trim().email('정확한 담당자 이메일 형식을 점검해 주십시오.'),
|
||||
managerPhone: z.string().trim().min(1, '담당자 연락처를 입력해 주십시오.'),
|
||||
priority: z.string(),
|
||||
totalRevenue: z.string().trim().optional(), // 총매출액(원)
|
||||
});
|
||||
|
||||
type FormValues = z.infer<typeof schema>;
|
||||
@ -42,7 +41,7 @@ function buildDefaults(mode: 'create' | 'edit', partner: Partner | null): FormVa
|
||||
managerName: partner.manager_name || '',
|
||||
managerEmail: partner.manager_email || '',
|
||||
managerPhone: partner.manager_contact_number || '',
|
||||
priority: partner.priority || 'MEDIUM',
|
||||
totalRevenue: partner.total_revenue != null ? String(partner.total_revenue) : '',
|
||||
};
|
||||
}
|
||||
return {
|
||||
@ -51,7 +50,7 @@ function buildDefaults(mode: 'create' | 'edit', partner: Partner | null): FormVa
|
||||
managerName: '',
|
||||
managerEmail: '',
|
||||
managerPhone: '010-',
|
||||
priority: 'MEDIUM',
|
||||
totalRevenue: '',
|
||||
};
|
||||
}
|
||||
|
||||
@ -68,7 +67,6 @@ export function PartnerFormSheet({
|
||||
}: PartnerFormSheetProps) {
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<FormValues>({
|
||||
@ -83,7 +81,7 @@ export function PartnerFormSheet({
|
||||
manager_name: v.managerName,
|
||||
manager_email: v.managerEmail,
|
||||
manager_contact_number: v.managerPhone,
|
||||
priority: v.priority,
|
||||
total_revenue: v.totalRevenue?.trim() ? Number(v.totalRevenue.replace(/[^0-9]/g, '')) : undefined,
|
||||
};
|
||||
|
||||
if (mode === 'create') {
|
||||
@ -138,26 +136,16 @@ export function PartnerFormSheet({
|
||||
/>
|
||||
{errors.code && <p className="text-[10px] text-rose-500">{errors.code.message}</p>}
|
||||
</div>
|
||||
{/* Priority */}
|
||||
{/* 총매출액 */}
|
||||
<div className="space-y-1">
|
||||
<Typography as="label" variant="label">우선 선정 대상자</Typography>
|
||||
<Controller
|
||||
control={control}
|
||||
name="priority"
|
||||
render={({ field }) => (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger id="form-partner-priority" className="w-full">
|
||||
<SelectValue>
|
||||
{(value) => priorityOptions.find((o) => o.value === value)?.label ?? ''}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{priorityOptions.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
<Typography as="label" variant="label">총매출액 (원, 선택)</Typography>
|
||||
<Input
|
||||
id="form-partner-revenue"
|
||||
type="number"
|
||||
min={0}
|
||||
{...register('totalRevenue')}
|
||||
className={inputClass}
|
||||
placeholder="예: 5000000000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { DataTable } from '@/components/ui/data-table';
|
||||
import { TablePagination } from '@/components/ui/table-pagination';
|
||||
import type { Partner } from '../types';
|
||||
@ -13,14 +12,6 @@ type PartnerTableProps = {
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
// 우선순위 배지 색상 — HIGH(빨강)/MEDIUM(주황)/그외(회색)
|
||||
const priorityBadgeClass = (priority?: string | null) =>
|
||||
priority === 'HIGH'
|
||||
? 'bg-red-50 text-red-700 dark:bg-rose-950/20 dark:text-rose-400 border border-red-200'
|
||||
: priority === 'MEDIUM'
|
||||
? 'bg-amber-50 text-amber-700 dark:bg-amber-950/20 dark:text-amber-400 border border-amber-200'
|
||||
: 'bg-zinc-100 text-zinc-600 border border-zinc-300';
|
||||
|
||||
export function PartnerTable({
|
||||
data,
|
||||
onRowClick,
|
||||
@ -74,16 +65,10 @@ export function PartnerTable({
|
||||
),
|
||||
},
|
||||
{
|
||||
header: '우선 선정 대상자',
|
||||
align: 'center',
|
||||
cell: (part) => (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`inline-flex items-center px-2 py-0.5 text-[10px] font-bold rounded-full ${priorityBadgeClass(part.priority)}`}
|
||||
>
|
||||
{part.priority}
|
||||
</Badge>
|
||||
),
|
||||
header: '총매출액',
|
||||
align: 'right',
|
||||
cellClassName: 'font-mono text-muted-foreground',
|
||||
cell: (part) => (part.total_revenue != null ? `₩${Number(part.total_revenue).toLocaleString()}` : '-'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -1,11 +1 @@
|
||||
export type { Partner } from '@/types';
|
||||
|
||||
// 우선순위 필터 목록. 'ALL'은 필터 전용(폼에서는 제외).
|
||||
export const prioritiesList = ['ALL', 'HIGH', 'MEDIUM', 'LOW'];
|
||||
|
||||
// 폼 우선순위 선택지(라벨 포함).
|
||||
export const priorityOptions: { value: string; label: string }[] = [
|
||||
{ value: 'HIGH', label: 'HIGH (핵심 조달처)' },
|
||||
{ value: 'MEDIUM', label: 'MEDIUM (일반 벤더)' },
|
||||
{ value: 'LOW', label: 'LOW (서브 보조처)' },
|
||||
];
|
||||
|
||||
@ -345,16 +345,9 @@ export function QuotationCreateModal({
|
||||
/>
|
||||
<div>
|
||||
<Typography as="span" variant="small" className="font-semibold block">{part.name}</Typography>
|
||||
<Typography as="span" variant="small" className="text-muted-foreground">이메일: {part.managerEmail} · 등급: {part.rank}</Typography>
|
||||
<Typography as="span" variant="small" className="text-muted-foreground">이메일: {part.managerEmail}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`text-[9px] font-mono px-2 py-0.5 rounded ${
|
||||
part.priority === 'HIGH' ? 'bg-red-50 text-red-700' : 'bg-zinc-100 text-zinc-600'
|
||||
}`}
|
||||
>
|
||||
{part.priority}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -78,7 +78,7 @@ export function RegenerateModal({ open, partners, sessionStatusBySupplier, defau
|
||||
<div className="min-w-0">
|
||||
<Typography as="span" variant="small" className="font-semibold block truncate">{part.name}</Typography>
|
||||
<Typography as="span" variant="small" className="text-muted-foreground">
|
||||
이메일: {part.managerEmail} · 등급: {part.rank}
|
||||
이메일: {part.managerEmail}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -55,7 +55,6 @@ export function mapSupplier(sp: SupplierData): Partner {
|
||||
managerName: sp.manager_name || '',
|
||||
managerEmail: sp.manager_email || '',
|
||||
managerPhone: sp.manager_contact_number || '',
|
||||
rank: sp.priority === 'HIGH' ? 'S' : sp.priority === 'MEDIUM' ? 'A' : 'B',
|
||||
status: 'ACTIVE',
|
||||
};
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import { showToast } from '@/lib/notify';
|
||||
import { confirm } from '@/lib/confirm';
|
||||
import { PageContainer } from '@/components/layout/PageContainer';
|
||||
import { PageToolbar, SearchInput } from '@/components/layout/PageToolbar';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu';
|
||||
import { useServerList } from '@/lib/useServerList';
|
||||
@ -13,15 +12,13 @@ import type { ListSuppliersParams } from '@/api/generated/model/listSuppliersPar
|
||||
import { PartnerTable } from '@/features/partners/components/PartnerTable';
|
||||
import { PartnerFormSheet } from '@/features/partners/components/PartnerFormSheet';
|
||||
import { ExcelUploadModal, downloadPartnerTemplate } from '@/features/partners/components/ExcelUploadModal';
|
||||
import { prioritiesList, type Partner } from '@/features/partners/types';
|
||||
import { type Partner } from '@/features/partners/types';
|
||||
|
||||
export default function PartnersPage() {
|
||||
// 검색/우선순위/페이지 상태(재사용 훅) → 서버 쿼리 파라미터로 변환.
|
||||
const list = useServerList({ pageSize: 10, initialFilters: { priority: 'ALL' } });
|
||||
const priorityFilter = list.filters.priority;
|
||||
// 검색/페이지 상태(재사용 훅) → 서버 쿼리 파라미터로 변환.
|
||||
const list = useServerList({ pageSize: 10 });
|
||||
const params: ListSuppliersParams = {
|
||||
search: list.debouncedSearch || undefined,
|
||||
priority: priorityFilter !== 'ALL' ? priorityFilter : undefined,
|
||||
page: list.page,
|
||||
size: list.pageSize,
|
||||
};
|
||||
@ -91,21 +88,6 @@ export default function PartnersPage() {
|
||||
onClear={list.clearSearch}
|
||||
placeholder="협력사명, 코드 또는 담당자명으로 추적 검색..."
|
||||
/>
|
||||
|
||||
<Select value={priorityFilter} onValueChange={(v) => list.setFilter('priority', v as string)}>
|
||||
<SelectTrigger id="partner-priority-filter" className="w-full sm:w-48">
|
||||
<SelectValue>
|
||||
{(value) => (value === 'ALL' ? '우선순위 가중치 (전체)' : `우선도: ${value}`)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{prioritiesList.map((prio) => (
|
||||
<SelectItem key={prio} value={prio}>
|
||||
{prio === 'ALL' ? '우선순위 가중치 (전체)' : `우선도: ${prio}`}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</PageToolbar>
|
||||
|
||||
<PartnerTable
|
||||
|
||||
@ -15,7 +15,6 @@ export type Partner = SupplierData & {
|
||||
managerName?: string;
|
||||
managerEmail?: string;
|
||||
managerPhone?: string;
|
||||
rank?: 'A' | 'B' | 'C' | 'S';
|
||||
status?: string;
|
||||
memo?: string;
|
||||
};
|
||||
|
||||
@ -130,7 +130,7 @@ CREATE TABLE IF NOT EXISTS partner.suppliers (
|
||||
manager_name VARCHAR(50) NULL, -- 담당자명
|
||||
manager_email VARCHAR(255) NULL, -- 담당자 이메일
|
||||
manager_contact_number VARCHAR(20) NULL, -- 담당자 연락처
|
||||
priority VARCHAR(10) NULL, -- 우선순위 (고객사별로 문자열 값일 수 있어 코드(SMALLINT) 대신 VARCHAR 유지)
|
||||
total_revenue BIGINT NULL, -- 총매출액(원). KTC suppliers.total_revenue 미러
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- 생성 시각(UTC)
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- 수정 시각(UTC, 앱에서 갱신)
|
||||
deleted BOOLEAN NOT NULL DEFAULT FALSE -- 소프트 삭제 여부
|
||||
@ -94,3 +94,11 @@ ALTER TABLE quotation.quotation_settings
|
||||
DROP COLUMN IF EXISTS over_action,
|
||||
DROP COLUMN IF EXISTS regen_limit,
|
||||
DROP COLUMN IF EXISTS anchoring_value;
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- [2026-07-06] 협력사: 총매출액 추가 + 우선선정(priority) 제거.
|
||||
-- priority(우선순위 문자열)와 그 파생 등급(rank)은 폐지 — 협력사에서 완전 제거.
|
||||
ALTER TABLE partner.suppliers
|
||||
ADD COLUMN IF NOT EXISTS total_revenue BIGINT; -- 총매출액(원, KTC total_revenue 미러)
|
||||
ALTER TABLE partner.suppliers
|
||||
DROP COLUMN IF EXISTS priority;
|
||||
Loading…
Reference in New Issue
Block a user