银川
1.新增开票管理页面
2.联调回款管理和回款计划页面
3.新增设备类型的子级时,没有展示出来
4.部署不同公司时配置公司api逻辑添加
| | |
| | | logo: string; |
| | | /** ç½é¡µæ é¢å¾æ è·¯å¾ï¼ç¸å¯¹äº public ç®å½ */ |
| | | favicon: string; |
| | | /** API æ¥å£å°å */ |
| | | apiUrl: string; |
| | | } |
| | | |
| | | /** |
| | |
| | | * 示ä¾ï¼set "COMPANY=TEST" |
| | | */ |
| | | export const companies: Record<string, CompanyConfig> = { |
| | | TEST: { name: 'æµè¯å
¬å¸', logo: '/logos/TEST.png', favicon: '/logos/TEST.ico' }, |
| | | TEST: { |
| | | name: 'æµè¯å
¬å¸', |
| | | logo: '/logos/TEST.png', |
| | | favicon: '/logos/TEST.ico', |
| | | apiUrl: 'http://42.63.70.90:9003/admin-api', |
| | | }, |
| | | }; |
| | |
| | | totalPrice: number; |
| | | depositPrice?: number; // å®ééé¢ï¼åä½ï¼å
|
| | | totalReceivablePrice: number; |
| | | totalInvoicePrice?: number; // å·²å¼ç¥¨éé¢ï¼å«å®¡æ¹ä¸ï¼ï¼åä½ï¼å
|
| | | unInvoicePrice?: number; // æªå¼ç¥¨éé¢ï¼åä½ï¼å
|
| | | signContactId: number; |
| | | signContactName?: string; |
| | | signUserId: number; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace CrmInvoiceApi { |
| | | /** ååä¿¡æ¯ */ |
| | | export interface Contract { |
| | | id?: number; |
| | | name?: string; |
| | | no: string; |
| | | } |
| | | |
| | | /** éä»¶ä¿¡æ¯ */ |
| | | export interface Attachment { |
| | | id: number; |
| | | url?: string; |
| | | name?: string; |
| | | } |
| | | |
| | | /** å¼ç¥¨ä¿¡æ¯ */ |
| | | export interface Invoice { |
| | | id?: number; |
| | | no?: string; |
| | | contractId?: number; |
| | | contract?: Contract; |
| | | customerId?: number; |
| | | customerName?: string; |
| | | invoiceType?: number; |
| | | invoiceNo?: string; |
| | | invoiceTitle?: string; |
| | | price?: number; |
| | | invoiceTime?: string; |
| | | auditStatus?: number; |
| | | remark?: string; |
| | | ownerUserId?: number; |
| | | ownerUserName?: string; |
| | | ownerUserDeptName?: string; |
| | | processInstanceId?: string; |
| | | attachmentList?: Attachment[]; |
| | | blobIds?: number[]; |
| | | hasAttachment?: boolean; |
| | | remainingInvoicePrice?: number; |
| | | creator?: string; |
| | | creatorName?: string; |
| | | createTime?: string; |
| | | updateTime?: string; |
| | | } |
| | | |
| | | /** å¼ç¥¨ç®è¦ä¿¡æ¯ï¼ç¨äºå款å
³èéæ©ï¼ */ |
| | | export interface InvoiceSimple { |
| | | id: number; |
| | | no: string; |
| | | invoiceNo?: string; |
| | | invoiceTitle?: string; |
| | | price?: number; |
| | | customerId?: number; |
| | | contractId?: number; |
| | | auditStatus?: number; |
| | | hasAttachment?: boolean; |
| | | remainingInvoicePrice?: number; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢å¼ç¥¨å页 */ |
| | | export function getInvoicePage(params: PageParam) { |
| | | return requestClient.get<PageResult<CrmInvoiceApi.Invoice>>( |
| | | '/crm/invoice/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢å¼ç¥¨å页ï¼åºäºæå®å®¢æ· */ |
| | | export function getInvoicePageByCustomer(params: PageParam) { |
| | | return requestClient.get<PageResult<CrmInvoiceApi.Invoice>>( |
| | | '/crm/invoice/page-by-customer', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢å¼ç¥¨è¯¦æ
*/ |
| | | export function getInvoice(id: number) { |
| | | return requestClient.get<CrmInvoiceApi.Invoice>( |
| | | `/crm/invoice/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢å¼ç¥¨ */ |
| | | export function createInvoice(data: CrmInvoiceApi.Invoice) { |
| | | return requestClient.post('/crm/invoice/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹å¼ç¥¨ */ |
| | | export function updateInvoice(data: CrmInvoiceApi.Invoice) { |
| | | return requestClient.put('/crm/invoice/update', data); |
| | | } |
| | | |
| | | /** å é¤å¼ç¥¨ */ |
| | | export function deleteInvoice(id: number) { |
| | | return requestClient.delete(`/crm/invoice/delete?id=${id}`); |
| | | } |
| | | |
| | | /** 导åºå¼ç¥¨ */ |
| | | export function exportInvoice(params: Record<string, unknown>) { |
| | | return requestClient.download('/crm/invoice/export-excel', { params }); |
| | | } |
| | | |
| | | /** æäº¤å®¡æ ¸ */ |
| | | export function submitInvoice(id: number) { |
| | | return requestClient.put(`/crm/invoice/submit?id=${id}`); |
| | | } |
| | | |
| | | /** è·å¾å¾
å®¡æ ¸å¼ç¥¨æ°é */ |
| | | export function getAuditInvoiceCount() { |
| | | return requestClient.get<number>('/crm/invoice/audit-count'); |
| | | } |
| | | |
| | | /** æ¥è¯¢å¼ç¥¨ä¸æåè¡¨ï¼æå忥å¯éå¼ç¥¨ï¼ */ |
| | | export function getInvoiceSimpleList(contractId: number) { |
| | | return requestClient.get<CrmInvoiceApi.InvoiceSimple[]>( |
| | | `/crm/invoice/simple-list?contractId=${contractId}`, |
| | | ); |
| | | } |
| | |
| | | CRM_PRODUCT = 6, // 产å |
| | | CRM_RECEIVABLE = 7, // 忬¾ |
| | | CRM_RECEIVABLE_PLAN = 8, // 忬¾è®¡å |
| | | CRM_INVOICE = 9, // å¼ç¥¨ |
| | | } |
| | | |
| | | /** |
| | |
| | | contractId?: number; |
| | | contractNo?: string; |
| | | contract?: Contract; |
| | | invoiceId?: number; |
| | | invoice?: InvoiceInfo; |
| | | auditStatus: number; |
| | | processInstanceId: number; |
| | | returnTime: Date; |
| | |
| | | totalPrice: number; |
| | | } |
| | | |
| | | /** å¼ç¥¨ä¿¡æ¯ */ |
| | | export interface InvoiceInfo { |
| | | id?: number; |
| | | no?: string; |
| | | invoiceNo?: string; |
| | | invoiceTitle?: string; |
| | | price?: number; |
| | | } |
| | | |
| | | export interface ReceivablePageParam extends PageParam { |
| | | contractId?: number; |
| | | customerId?: number; |
| | |
| | | CRM_PRODUCT_STATUS: 'crm_product_status', // CRM ååç¶æ |
| | | CRM_PRODUCT_UNIT: 'crm_product_unit', // CRM 产ååä½ |
| | | CRM_RECEIVABLE_RETURN_TYPE: 'crm_receivable_return_type', // CRM 忬¾çè¿æ¬¾æ¹å¼ |
| | | CRM_INVOICE_TYPE: 'crm_invoice_type', // CRM å票类å |
| | | } as const; |
| | | |
| | | /** ========== AFTERSALES - å®åæå¡æ¨¡å ========== */ |
| | |
| | | }, |
| | | component: () => import('#/views/crm/product/detail/index.vue'), |
| | | }, |
| | | { |
| | | path: 'invoice/detail/:id', |
| | | name: 'CrmInvoiceDetail', |
| | | meta: { |
| | | title: 'å¼ç¥¨è¯¦æ
', |
| | | activePath: '/crm/invoice', |
| | | }, |
| | | component: () => import('#/views/crm/invoice/detail/index.vue'), |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | |
| | | }, |
| | | }, |
| | | { |
| | | title: 'å·²å¼ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'totalInvoicePrice', |
| | | minWidth: 150, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'æªå¼ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'unInvoicePrice', |
| | | minWidth: 150, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'è´è´£äºº', |
| | | field: 'ownerUserName', |
| | | minWidth: 150, |
| | |
| | | }, |
| | | }, |
| | | { |
| | | title: 'å·²å¼ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'totalInvoicePrice', |
| | | minWidth: 150, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'æªå¼ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'unInvoicePrice', |
| | | minWidth: 140, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'æåè·è¿æ¶é´', |
| | | field: 'contactLastTime', |
| | | minWidth: 180, |
| | |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| | | }, |
| | | { |
| | | field: 'totalInvoicePrice', |
| | | label: 'å·²å¼ç¥¨éé¢ï¼å«å®¡æ¹ä¸ï¼', |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| | | }, |
| | | { |
| | | field: 'ownerUserName', |
| | | label: 'è´è´£äºº', |
| | | }, |
| | |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| | | }, |
| | | { |
| | | field: 'totalInvoicePrice', |
| | | label: 'å·²å¼ç¥¨éé¢ï¼å«å®¡æ¹ä¸ï¼', |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| | | }, |
| | | { |
| | | field: 'unInvoicePrice', |
| | | label: 'æªå¼ç¥¨éé¢ï¼å
ï¼', |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| | | }, |
| | | { |
| | | field: 'depositPrice', |
| | | label: 'å®ééé¢ï¼å
ï¼', |
| | | render: (val) => erpPriceInputFormatter(val) as string, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { getDictOptions } from '@vben/hooks'; |
| | | import { useUserStore } from '@vben/stores'; |
| | | |
| | | import { getContractSimpleList } from '#/api/crm/contract'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import CrmCustomerSelect from '#/components/crm-customer-select.vue'; |
| | | |
| | | import { markRaw } from 'vue'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema(): VbenFormSchema[] { |
| | | const userStore = useUserStore(); |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'no', |
| | | label: 'å¼ç¥¨ç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: 'ä¿åæ¶èªå¨çæ', |
| | | disabled: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'ownerUserId', |
| | | label: 'è´è´£äºº', |
| | | component: 'ApiSelect', |
| | | rules: 'required', |
| | | dependencies: { |
| | | triggerFields: ['id'], |
| | | disabled: (values) => !!values.id, |
| | | }, |
| | | componentProps: { |
| | | api: getSimpleUserList, |
| | | labelField: 'nickname', |
| | | valueField: 'id', |
| | | placeholder: 'è¯·éæ©è´è´£äºº', |
| | | allowClear: true, |
| | | }, |
| | | defaultValue: userStore.userInfo?.id, |
| | | }, |
| | | { |
| | | fieldName: 'customerId', |
| | | label: '客æ·åç§°', |
| | | component: markRaw(CrmCustomerSelect), |
| | | rules: 'required', |
| | | componentProps: (_values, form) => ({ |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | onChange: () => { |
| | | form.setFieldValue('contractId', undefined); |
| | | form.setFieldValue('price', undefined); |
| | | }, |
| | | }), |
| | | dependencies: { |
| | | triggerFields: ['id'], |
| | | disabled: (values) => !!values.id, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contractId', |
| | | label: 'åååç§°', |
| | | component: 'Select', |
| | | rules: 'required', |
| | | dependencies: { |
| | | triggerFields: ['customerId'], |
| | | disabled: (values) => !values.customerId || !!values.id, |
| | | async componentProps(values, form) { |
| | | if (!values.customerId) { |
| | | return { |
| | | options: [], |
| | | placeholder: '请å
鿩客æ·', |
| | | }; |
| | | } |
| | | const contracts = await getContractSimpleList(values.customerId); |
| | | const approvedContracts = contracts.filter( |
| | | (item) => item.auditStatus === 20, |
| | | ); |
| | | return { |
| | | options: approvedContracts.map((item) => { |
| | | const remain = |
| | | (item.totalPrice ?? 0) - (item.totalInvoicePrice ?? 0); |
| | | return { |
| | | label: `${item.name}ï¼Â¥${item.totalPrice ?? 0}ï¼å¯å¼ç¥¨ Â¥${remain.toFixed(2)}ï¼`, |
| | | value: item.id, |
| | | }; |
| | | }), |
| | | placeholder: 'è¯·éæ©åå', |
| | | onChange: () => { |
| | | form.setFieldValue('price', undefined); |
| | | }, |
| | | } as any; |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'invoiceType', |
| | | label: 'å票类å', |
| | | component: 'Select', |
| | | rules: 'required', |
| | | componentProps: { |
| | | options: getDictOptions(DICT_TYPE.CRM_INVOICE_TYPE, 'number'), |
| | | placeholder: 'è¯·éæ©å票类å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'invoiceNo', |
| | | label: 'å票å·ç ', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å票å·ç ', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'invoiceTitle', |
| | | label: 'å票æ¬å¤´', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å票æ¬å¤´', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'price', |
| | | label: 'å¼ç¥¨éé¢ï¼å
ï¼', |
| | | component: 'InputNumber', |
| | | rules: 'required', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | placeholder: '请è¾å
¥å¼ç¥¨éé¢', |
| | | min: 0, |
| | | precision: 2, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'invoiceTime', |
| | | label: 'å¼ç¥¨æ¥æ', |
| | | component: 'DatePicker', |
| | | rules: 'required', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©å¼ç¥¨æ¥æ', |
| | | showTime: false, |
| | | valueFormat: 'YYYY-MM-DD', |
| | | format: 'YYYY-MM-DD', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'blobIds', |
| | | label: 'å票éä»¶', |
| | | component: 'FileUpload', |
| | | componentProps: { |
| | | valueKey: 'id', |
| | | maxNumber: 10, |
| | | multiple: true, |
| | | accept: ['jpg', 'jpeg', 'png', 'pdf'], |
| | | helpText: 'æ¯æjpgãjpegãpngãpdfæ ¼å¼ï¼å个æä»¶ä¸è¶
è¿2MB', |
| | | }, |
| | | formItemClass: 'md:col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 4, |
| | | }, |
| | | formItemClass: 'md:col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'no', |
| | | label: 'å¼ç¥¨ç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¼ç¥¨ç¼å·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'customerId', |
| | | label: '客æ·', |
| | | component: markRaw(CrmCustomerSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function useGridColumns(): VxeTableGridOptions['columns'] { |
| | | return [ |
| | | { |
| | | title: 'å¼ç¥¨ç¼å·', |
| | | field: 'no', |
| | | minWidth: 160, |
| | | fixed: 'left', |
| | | slots: { default: 'no' }, |
| | | }, |
| | | { |
| | | title: '客æ·åç§°', |
| | | field: 'customerName', |
| | | minWidth: 150, |
| | | slots: { default: 'customerName' }, |
| | | }, |
| | | { |
| | | title: 'ååç¼å·', |
| | | field: 'contract', |
| | | minWidth: 160, |
| | | slots: { default: 'contractNo' }, |
| | | }, |
| | | { |
| | | title: 'å票类å', |
| | | field: 'invoiceType', |
| | | minWidth: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.CRM_INVOICE_TYPE }, |
| | | }, |
| | | }, |
| | | { |
| | | title: 'å票å·ç ', |
| | | field: 'invoiceNo', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | title: 'å¼ç¥¨éé¢ï¼å
ï¼', |
| | | field: 'price', |
| | | minWidth: 150, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'å©ä½å¯å款éé¢ï¼å«å®¡æ¹ä¸ï¼', |
| | | field: 'remainingInvoicePrice', |
| | | minWidth: 180, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'å¼ç¥¨æ¥æ', |
| | | field: 'invoiceTime', |
| | | minWidth: 150, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: '夿³¨', |
| | | field: 'remark', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | title: 'è´è´£äºº', |
| | | field: 'ownerUserName', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | title: 'æå±é¨é¨', |
| | | field: 'ownerUserDeptName', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | title: 'æ´æ°æ¶é´', |
| | | field: 'updateTime', |
| | | minWidth: 150, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'å建æ¶é´', |
| | | field: 'createTime', |
| | | minWidth: 150, |
| | | formatter: 'formatDateTime', |
| | | }, |
| | | { |
| | | title: 'å建人', |
| | | field: 'creatorName', |
| | | minWidth: 150, |
| | | }, |
| | | { |
| | | title: 'éä»¶', |
| | | field: 'attachmentList', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'attachment' }, |
| | | }, |
| | | { |
| | | title: '审æ¹ç¶æ', |
| | | field: 'auditStatus', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'auditStatus' }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | field: 'actions', |
| | | minWidth: 200, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { DescriptionItemSchema } from '#/components/description'; |
| | | |
| | | import { h } from 'vue'; |
| | | |
| | | import { DICT_TYPE } from '@vben/constants'; |
| | | import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; |
| | | |
| | | import { DictTag } from '#/components/dict-tag'; |
| | | |
| | | /** 详æ
页çåæ®µ */ |
| | | export function useDetailSchema(): DescriptionItemSchema[] { |
| | | return [ |
| | | { |
| | | field: 'customerName', |
| | | label: '客æ·åç§°', |
| | | }, |
| | | { |
| | | field: 'price', |
| | | label: 'å¼ç¥¨éé¢ï¼å
ï¼', |
| | | render: (val) => erpPriceInputFormatter(val), |
| | | }, |
| | | { |
| | | field: 'remainingInvoicePrice', |
| | | label: 'å©ä½å¯å款éé¢ï¼å«å®¡æ¹ä¸ï¼', |
| | | render: (val) => erpPriceInputFormatter(val ?? 0), |
| | | }, |
| | | { |
| | | field: 'invoiceTime', |
| | | label: 'å¼ç¥¨æ¥æ', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | field: 'invoiceType', |
| | | label: 'å票类å', |
| | | render: (val) => |
| | | h(DictTag, { |
| | | type: DICT_TYPE.CRM_INVOICE_TYPE, |
| | | value: val, |
| | | }), |
| | | }, |
| | | { |
| | | field: 'invoiceTitle', |
| | | label: 'å票æ¬å¤´', |
| | | }, |
| | | { |
| | | field: 'invoiceNo', |
| | | label: 'å票å·ç ', |
| | | }, |
| | | { |
| | | field: 'ownerUserName', |
| | | label: 'è´è´£äºº', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** 详æ
页çåºç¡å段 */ |
| | | export function useDetailBaseSchema(): DescriptionItemSchema[] { |
| | | return [ |
| | | { |
| | | field: 'no', |
| | | label: 'å¼ç¥¨ç¼å·', |
| | | }, |
| | | { |
| | | field: 'customerName', |
| | | label: '客æ·åç§°', |
| | | }, |
| | | { |
| | | field: 'contract', |
| | | label: 'ååç¼å·', |
| | | render: (val, data) => |
| | | val && data?.contract?.no ? data?.contract?.no : '', |
| | | }, |
| | | { |
| | | field: 'invoiceType', |
| | | label: 'å票类å', |
| | | render: (val) => |
| | | h(DictTag, { |
| | | type: DICT_TYPE.CRM_INVOICE_TYPE, |
| | | value: val, |
| | | }), |
| | | }, |
| | | { |
| | | field: 'invoiceNo', |
| | | label: 'å票å·ç ', |
| | | }, |
| | | { |
| | | field: 'invoiceTitle', |
| | | label: 'å票æ¬å¤´', |
| | | }, |
| | | { |
| | | field: 'price', |
| | | label: 'å¼ç¥¨éé¢', |
| | | render: (val) => erpPriceInputFormatter(val), |
| | | }, |
| | | { |
| | | field: 'remainingInvoicePrice', |
| | | label: 'å©ä½å¯å款éé¢ï¼å«å®¡æ¹ä¸ï¼', |
| | | render: (val) => erpPriceInputFormatter(val ?? 0), |
| | | }, |
| | | { |
| | | field: 'invoiceTime', |
| | | label: 'å¼ç¥¨æ¥æ', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | field: 'remark', |
| | | label: '夿³¨', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** ç³»ç»ä¿¡æ¯å段 */ |
| | | export function useDetailSystemSchema(): DescriptionItemSchema[] { |
| | | return [ |
| | | { |
| | | field: 'ownerUserName', |
| | | label: 'è´è´£äºº', |
| | | }, |
| | | { |
| | | field: 'creatorName', |
| | | label: 'å建人', |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | label: 'å建æ¶é´', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | { |
| | | field: 'updateTime', |
| | | label: 'æ´æ°æ¶é´', |
| | | render: (val) => formatDateTime(val) as string, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup lang="ts"> |
| | | import type { CrmInvoiceApi } from '#/api/crm/invoice'; |
| | | import type { SystemOperateLogApi } from '#/api/system/operate-log'; |
| | | |
| | | import { onMounted, ref } from 'vue'; |
| | | import { useRoute, useRouter } from 'vue-router'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { useTabs } from '@vben/hooks'; |
| | | |
| | | import { Card, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { getInvoice } from '#/api/crm/invoice'; |
| | | import { getOperateLogPage } from '#/api/crm/operateLog'; |
| | | import { BizTypeEnum } from '#/api/crm/permission'; |
| | | import { useDescription } from '#/components/description'; |
| | | import { OperateLog } from '#/components/operate-log'; |
| | | import { ACTION_ICON, TableAction } from '#/components/table-action'; |
| | | import { $t } from '#/locales'; |
| | | import { PermissionList } from '#/views/crm/permission'; |
| | | |
| | | import InvoiceForm from '../modules/form.vue'; |
| | | import { useDetailSchema } from './data'; |
| | | import Info from './modules/info.vue'; |
| | | |
| | | const props = defineProps<{ id?: number }>(); |
| | | |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const tabs = useTabs(); |
| | | |
| | | const loading = ref(false); |
| | | const invoiceId = ref(0); |
| | | const invoice = ref<CrmInvoiceApi.Invoice>({} as CrmInvoiceApi.Invoice); |
| | | const logList = ref<SystemOperateLogApi.OperateLog[]>([]); |
| | | const permissionListRef = ref<InstanceType<typeof PermissionList>>(); |
| | | |
| | | const [Descriptions] = useDescription({ |
| | | bordered: false, |
| | | column: 4, |
| | | schema: useDetailSchema(), |
| | | }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: InvoiceForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å è½½å¼ç¥¨è¯¦æ
*/ |
| | | async function loadInvoiceDetail() { |
| | | loading.value = true; |
| | | try { |
| | | invoice.value = await getInvoice(invoiceId.value); |
| | | const res = await getOperateLogPage({ |
| | | bizType: BizTypeEnum.CRM_INVOICE, |
| | | bizId: invoiceId.value, |
| | | }); |
| | | logList.value = res.list; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | /** è¿åå表页 */ |
| | | function handleBack() { |
| | | tabs.closeCurrentTab(); |
| | | router.push({ name: 'CrmInvoice' }); |
| | | } |
| | | |
| | | /** ç¼è¾å¼ç¥¨ */ |
| | | function handleEdit() { |
| | | formModalApi.setData({ invoice: { id: invoiceId.value } }).open(); |
| | | } |
| | | |
| | | /** å è½½æ°æ® */ |
| | | onMounted(() => { |
| | | invoiceId.value = Number(props.id || route.params.id); |
| | | loadInvoiceDetail(); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height :title="invoice?.no" :loading="loading"> |
| | | <FormModal @success="loadInvoiceDetail" /> |
| | | <template #extra> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'è¿å', |
| | | type: 'default', |
| | | icon: 'lucide:arrow-left', |
| | | onClick: handleBack, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.edit'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['crm:invoice:update'], |
| | | ifShow: permissionListRef?.validateWrite, |
| | | onClick: handleEdit, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <Card class="min-h-[10%]"> |
| | | <Descriptions :data="invoice" /> |
| | | </Card> |
| | | <Card class="mt-4 min-h-[60%]"> |
| | | <Tabs> |
| | | <Tabs.TabPane tab="详ç»èµæ" key="1" :force-render="true"> |
| | | <Info :invoice="invoice" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane tab="æä½æ¥å¿" key="2" :force-render="true"> |
| | | <OperateLog :log-list="logList" /> |
| | | </Tabs.TabPane> |
| | | <Tabs.TabPane tab="å¢éæå" key="3" :force-render="true"> |
| | | <PermissionList |
| | | ref="permissionListRef" |
| | | :biz-id="invoiceId" |
| | | :biz-type="BizTypeEnum.CRM_INVOICE" |
| | | :show-action="true" |
| | | @quit-team="handleBack" |
| | | /> |
| | | </Tabs.TabPane> |
| | | </Tabs> |
| | | </Card> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmInvoiceApi } from '#/api/crm/invoice'; |
| | | |
| | | import { Divider } from 'ant-design-vue'; |
| | | |
| | | import { useDescription } from '#/components/description'; |
| | | |
| | | import { useDetailBaseSchema, useDetailSystemSchema } from '../data'; |
| | | |
| | | const props = defineProps<{ |
| | | invoice: CrmInvoiceApi.Invoice; |
| | | }>(); |
| | | |
| | | const [BaseDescriptions] = useDescription({ |
| | | title: 'åºæ¬ä¿¡æ¯', |
| | | bordered: false, |
| | | column: 4, |
| | | class: 'mx-4', |
| | | schema: useDetailBaseSchema(), |
| | | }); |
| | | |
| | | const [SystemDescriptions] = useDescription({ |
| | | title: 'ç³»ç»ä¿¡æ¯', |
| | | bordered: false, |
| | | column: 3, |
| | | class: 'mx-4', |
| | | schema: useDetailSystemSchema(), |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <div> |
| | | <BaseDescriptions :data="invoice" /> |
| | | <Divider /> |
| | | <div v-if="props.invoice?.attachmentList?.length" class="mx-4 mb-4"> |
| | | <h4 class="mb-2 text-base font-medium">å票éä»¶</h4> |
| | | <div |
| | | v-for="file in props.invoice.attachmentList" |
| | | :key="file.id" |
| | | class="flex items-center gap-2 py-1" |
| | | > |
| | | <a :href="file.url" target="_blank" rel="noopener noreferrer"> |
| | | {{ file.name || 'éä»¶' }} |
| | | </a> |
| | | </div> |
| | | </div> |
| | | <SystemDescriptions :data="invoice" /> |
| | | </div> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { CrmInvoiceApi } from '#/api/crm/invoice'; |
| | | |
| | | import { ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { Button, message, Tag, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteInvoice, |
| | | exportInvoice, |
| | | getInvoicePage, |
| | | submitInvoice, |
| | | } from '#/api/crm/invoice'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | const { push } = useRouter(); |
| | | const sceneType = ref('1'); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** å¤çåºæ¯ç±»åç忢 */ |
| | | function handleChangeSceneType(key: number | string) { |
| | | sceneType.value = key.toString(); |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const formValues = await gridApi.formApi.getValues(); |
| | | const data = await exportInvoice({ |
| | | sceneType: sceneType.value, |
| | | ...formValues, |
| | | }); |
| | | downloadFileFromBlobPart({ fileName: 'å¼ç¥¨.xls', source: data }); |
| | | } |
| | | |
| | | /** å建å¼ç¥¨ */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** ç¼è¾å¼ç¥¨ */ |
| | | function handleEdit(row: CrmInvoiceApi.Invoice) { |
| | | formModalApi.setData({ invoice: row }).open(); |
| | | } |
| | | |
| | | /** å é¤å¼ç¥¨ */ |
| | | async function handleDelete(row: CrmInvoiceApi.Invoice) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.no]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteInvoice(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.no])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æäº¤å®¡æ ¸ */ |
| | | async function handleSubmit(row: CrmInvoiceApi.Invoice) { |
| | | const hideLoading = message.loading({ |
| | | content: 'æäº¤å®¡æ ¸ä¸...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await submitInvoice(row.id!); |
| | | message.success('æäº¤å®¡æ ¸æå'); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ¥çå¼ç¥¨è¯¦æ
*/ |
| | | function handleDetail(row: CrmInvoiceApi.Invoice) { |
| | | push({ name: 'CrmInvoiceDetail', params: { id: row.id } }); |
| | | } |
| | | |
| | | /** æ¥ç客æ·è¯¦æ
*/ |
| | | function handleCustomerDetail(row: CrmInvoiceApi.Invoice) { |
| | | push({ name: 'CrmCustomerDetail', params: { id: row.customerId } }); |
| | | } |
| | | |
| | | /** æ¥çåå详æ
*/ |
| | | function handleContractDetail(row: CrmInvoiceApi.Invoice) { |
| | | push({ name: 'CrmContractDetail', params: { id: row.contractId } }); |
| | | } |
| | | |
| | | /** æ¥ç审æ¹è¯¦æ
*/ |
| | | function handleProcessDetail(row: CrmInvoiceApi.Invoice) { |
| | | push({ |
| | | name: 'BpmProcessInstanceDetail', |
| | | query: { id: row.processInstanceId }, |
| | | }); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getInvoicePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | sceneType: sceneType.value, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<CrmInvoiceApi.Invoice>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid> |
| | | <template #toolbar-actions> |
| | | <Tabs class="w-full" @change="handleChangeSceneType"> |
| | | <Tabs.TabPane tab="æè´è´£ç" key="1" /> |
| | | <Tabs.TabPane tab="æåä¸ç" key="2" /> |
| | | <Tabs.TabPane tab="ä¸å±è´è´£ç" key="3" /> |
| | | </Tabs> |
| | | </template> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'æ°å¢å¼ç¥¨ç³è¯·', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['crm:invoice:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['crm:invoice:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #no="{ row }"> |
| | | <Button type="link" @click="handleDetail(row)"> |
| | | {{ row.no }} |
| | | </Button> |
| | | </template> |
| | | <template #customerName="{ row }"> |
| | | <Button type="link" @click="handleCustomerDetail(row)"> |
| | | {{ row.customerName }} |
| | | </Button> |
| | | </template> |
| | | <template #contractNo="{ row }"> |
| | | <Button |
| | | v-if="row.contract" |
| | | type="link" |
| | | @click="handleContractDetail(row)" |
| | | > |
| | | {{ row.contract.no }} |
| | | </Button> |
| | | <span v-else>--</span> |
| | | </template> |
| | | <template #attachment="{ row }"> |
| | | <Tag v-if="!row.hasAttachment" color="warning"> |
| | | æªä¸ä¼ |
| | | </Tag> |
| | | <Tag |
| | | v-else |
| | | color="success" |
| | | style="cursor: pointer" |
| | | @click="handleDetail(row)" |
| | | > |
| | | å·²ä¸ä¼ |
| | | </Tag> |
| | | </template> |
| | | <template #auditStatus="{ row }"> |
| | | <Tag v-if="row.auditStatus === 0" color="default">æªæäº¤</Tag> |
| | | <Tag v-if="row.auditStatus === 10" color="warning">审æ¹ä¸</Tag> |
| | | <Tag v-if="row.auditStatus === 20" color="success">å®¡æ ¸éè¿</Tag> |
| | | <Tag v-if="row.auditStatus === 30" color="error">å®¡æ ¸ä¸éè¿</Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['crm:invoice:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'æäº¤å®¡æ ¸', |
| | | type: 'link', |
| | | auth: ['crm:invoice:update'], |
| | | onClick: handleSubmit.bind(null, row), |
| | | ifShow: row.auditStatus === 0, |
| | | }, |
| | | { |
| | | label: 'æ¥ç审æ¹', |
| | | type: 'link', |
| | | auth: ['crm:invoice:update'], |
| | | onClick: handleProcessDetail.bind(null, row), |
| | | ifShow: row.auditStatus !== 0 && !!row.processInstanceId, |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['crm:invoice:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.no]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmInvoiceApi } from '#/api/crm/invoice'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenForm, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { getContractSimpleList } from '#/api/crm/contract'; |
| | | import { |
| | | createInvoice, |
| | | getInvoice, |
| | | updateInvoice, |
| | | } from '#/api/crm/invoice'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | type InvoicePrefillData = Partial< |
| | | Pick<CrmInvoiceApi.Invoice, 'contractId' | 'customerId'> |
| | | >; |
| | | |
| | | type InvoiceFormModalData = InvoicePrefillData & { |
| | | invoice?: Pick<CrmInvoiceApi.Invoice, 'id'>; |
| | | }; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<Partial<CrmInvoiceApi.Invoice>>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['å¼ç¥¨']) |
| | | : $t('ui.actionTitle.create', ['å¼ç¥¨']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | labelWidth: 120, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | /** æå»ºæ°å¢å¼ç¥¨çé¢å¡«è¡¨å */ |
| | | async function buildCreateFormData( |
| | | prefill: InvoicePrefillData, |
| | | ): Promise<Partial<CrmInvoiceApi.Invoice>> { |
| | | const values: Partial<CrmInvoiceApi.Invoice> = { |
| | | contractId: prefill?.contractId, |
| | | customerId: prefill?.customerId, |
| | | }; |
| | | if (values.customerId && values.contractId) { |
| | | const contracts = await getContractSimpleList(values.customerId); |
| | | const contract = contracts.find((item) => item.id === values.contractId); |
| | | if (contract) { |
| | | values.invoiceTitle = contract.customerName; |
| | | } |
| | | } |
| | | return values; |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = (await formApi.getValues()) as CrmInvoiceApi.Invoice; |
| | | if (formData.value?.id) { |
| | | data.id = formData.value.id; |
| | | } |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateInvoice(data) |
| | | : createInvoice(data)); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | formData.value = undefined; |
| | | await formApi.resetForm(); |
| | | const data = modalApi.getData() as null | InvoiceFormModalData; |
| | | if (!data) { |
| | | return; |
| | | } |
| | | const { invoice } = data; |
| | | const prefill = data.customerId || data.contractId ? data : undefined; |
| | | modalApi.lock(); |
| | | try { |
| | | if (invoice?.id) { |
| | | formData.value = await getInvoice(invoice.id!); |
| | | // ç¨ attachmentList ä»£æ¿ blobIdsï¼è®©ä¸ä¼ ç»ä»¶è½å±ç¤ºæä»¶ååé¢è§é¾æ¥ |
| | | if (formData.value?.attachmentList?.length) { |
| | | (formData.value as Record<string, unknown>).blobIds = |
| | | formData.value.attachmentList; |
| | | } |
| | | } else if (prefill) { |
| | | formData.value = await buildCreateFormData(prefill); |
| | | } |
| | | if (formData.value) { |
| | | await formApi.setValues(formData.value as any); |
| | | } |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | import { useUserStore } from '@vben/stores'; |
| | | |
| | | import { getContractSimpleList } from '#/api/crm/contract'; |
| | | import { getInvoiceSimpleList } from '#/api/crm/invoice'; |
| | | import { |
| | | getReceivablePlan, |
| | | getReceivablePlanSimpleList, |
| | |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | onChange: () => { |
| | | form.setFieldValue('contractId', undefined); |
| | | form.setFieldValue('invoiceId', undefined); |
| | | form.setFieldValue('planId', undefined); |
| | | form.setFieldValue('price', undefined); |
| | | form.setFieldValue('returnTime', undefined); |
| | |
| | | })), |
| | | placeholder: 'è¯·éæ©åå', |
| | | onChange: (value: number) => { |
| | | form.setFieldValue('invoiceId', undefined); |
| | | form.setFieldValue('planId', undefined); |
| | | form.setFieldValue('returnTime', undefined); |
| | | form.setFieldValue('returnType', undefined); |
| | |
| | | ? contract.totalPrice - contract.totalReceivablePrice |
| | | : undefined, |
| | | ); |
| | | }, |
| | | } as any; |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'invoiceId', |
| | | label: 'å
³èå¼ç¥¨', |
| | | component: 'Select', |
| | | rules: 'required', |
| | | dependencies: { |
| | | triggerFields: ['contractId', 'invoiceId'], |
| | | disabled: (values) => !values.contractId || !!values.id, |
| | | async componentProps(values, form) { |
| | | if (!values.contractId) { |
| | | return { |
| | | options: [], |
| | | placeholder: '请å
éæ©åå', |
| | | }; |
| | | } |
| | | const invoices = await getInvoiceSimpleList(values.contractId); |
| | | return { |
| | | options: invoices.map((item) => { |
| | | const remain = item.remainingInvoicePrice ?? item.price ?? 0; |
| | | return { |
| | | label: `${item.no} / ${item.invoiceNo ?? '-'} / Â¥${item.price ?? 0} / å©ä½å¯å款 Â¥${remain.toFixed(2)}`, |
| | | value: item.id, |
| | | }; |
| | | }), |
| | | placeholder: 'è¯·éæ©å
³èå¼ç¥¨', |
| | | onChange: (value: number) => { |
| | | form.setFieldValue('planId', undefined); |
| | | form.setFieldValue('returnTime', undefined); |
| | | form.setFieldValue('returnType', undefined); |
| | | const invoice = invoices.find((item) => item.id === value); |
| | | if (invoice) { |
| | | form.setFieldValue( |
| | | 'price', |
| | | invoice.remainingInvoicePrice ?? invoice.price ?? undefined, |
| | | ); |
| | | form.setFieldValue('customerId', invoice.customerId); |
| | | } |
| | | }, |
| | | } as any; |
| | | }, |
| | |
| | | slots: { default: 'contractNo' }, |
| | | }, |
| | | { |
| | | title: 'å
³èå¼ç¥¨', |
| | | field: 'invoice', |
| | | minWidth: 200, |
| | | slots: { default: 'invoiceNo' }, |
| | | }, |
| | | { |
| | | title: '忬¾æ¥æ', |
| | | field: 'returnTime', |
| | | minWidth: 150, |
| | |
| | | val && data?.contract?.no ? data?.contract?.no : '', |
| | | }, |
| | | { |
| | | field: 'invoice', |
| | | label: 'å
³èå¼ç¥¨', |
| | | render: (val, data) => |
| | | val && data?.invoice?.no |
| | | ? `${data.invoice.no} / ${data.invoice.invoiceNo ?? '-'} / ¥${data.invoice.price ?? 0}` |
| | | : '', |
| | | }, |
| | | { |
| | | field: 'returnTime', |
| | | label: '忬¾æ¥æ', |
| | | render: (val) => formatDateTime(val) as string, |
| | |
| | | push({ name: 'CrmContractDetail', params: { id: row.contractId } }); |
| | | } |
| | | |
| | | /** æ¥çå¼ç¥¨è¯¦æ
*/ |
| | | function handleInvoiceDetail(row: CrmReceivableApi.Receivable) { |
| | | push({ name: 'CrmInvoiceDetail', params: { id: row.invoiceId } }); |
| | | } |
| | | |
| | | /** æ¥ç审æ¹è¯¦æ
*/ |
| | | function handleProcessDetail(row: CrmReceivableApi.Receivable) { |
| | | push({ |
| | |
| | | {{ row.customerName }} |
| | | </Button> |
| | | </template> |
| | | <template #invoiceNo="{ row }"> |
| | | <template v-if="row.invoice"> |
| | | <Button type="link" @click="handleInvoiceDetail(row)"> |
| | | {{ row.invoice.no }} |
| | | </Button> |
| | | <span class="mx-1">/</span> |
| | | <span>{{ row.invoice.invoiceNo ?? '-' }}</span> |
| | | <span class="mx-1">/</span> |
| | | <span>Â¥{{ row.invoice.price ?? 0 }}</span> |
| | | </template> |
| | | <span v-else>--</span> |
| | | </template> |
| | | <template #contractNo="{ row }"> |
| | | <Button |
| | | v-if="row.contract" |
| | |
| | | <script lang="ts" setup> |
| | | import type { CrmReceivableApi } from '#/api/crm/receivable'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | import { computed, ref, watch } from 'vue'; |
| | | |
| | | import { useVbenForm, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import { Alert, message } from 'ant-design-vue'; |
| | | |
| | | import { getContractSimpleList } from '#/api/crm/contract'; |
| | | import { |
| | |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<Partial<CrmReceivableApi.Receivable>>(); |
| | | const showInvoiceHint = ref(false); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['忬¾']) |
| | |
| | | schema: useFormSchema(), |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | /** çå¬ååéæ©ï¼æç¤ºå
å¼ç¥¨å忬¾ */ |
| | | watch( |
| | | () => formApi.form?.values?.contractId, |
| | | (val) => { |
| | | showInvoiceHint.value = !!val; |
| | | }, |
| | | ); |
| | | |
| | | /** æå»ºæ°å¢å款çé¢å¡«è¡¨å */ |
| | | async function buildCreateFormData( |
| | |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-2/5"> |
| | | <Alert |
| | | v-if="showInvoiceHint" |
| | | type="warning" |
| | | message="请å
宿å¼ç¥¨å¹¶ä¸ä¼ å票éä»¶ï¼åè¿è¡å款æä½" |
| | | show-icon |
| | | class="mb-4" |
| | | /> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import CrmCustomerSelect from '#/components/crm-customer-select.vue'; |
| | | |
| | | import dayjs from 'dayjs'; |
| | | import { markRaw } from 'vue'; |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | |
| | | valueFormat: 'YYYY-MM-DD', |
| | | format: 'YYYY-MM-DD', |
| | | }, |
| | | defaultValue: new Date(), |
| | | defaultValue: dayjs().format('YYYY-MM-DD'), |
| | | }, |
| | | { |
| | | fieldName: 'remindDays', |
| | |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | labelWidth: 120, |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | |
| | | return; |
| | | } |
| | | // å è½½æ°æ® |
| | | formData.value = undefined; |
| | | await formApi.resetForm(); |
| | | const data = modalApi.getData<CrmReceivablePlanApi.Plan>(); |
| | | if (!data || !data.id) { |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(data); |
| | | if (!data) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | if (data.id) { |
| | | formData.value = await getReceivablePlan(data.id); |
| | | } |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(formData.value); |
| | | await formApi.setValues(formData.value ?? data); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-2/5"> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { handleTree } from '@vben/utils'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async (_, formValues) => |
| | | handleTree(await getMachineryTypeList(formValues)), |
| | | await getMachineryTypeList(formValues), |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | |
| | | import { resolve } from "node:path"; |
| | | import { loadEnv } from "vite"; |
| | | import { defineConfig } from "./internal/vite-config/src"; |
| | | |
| | | /** å
¬å¸é
ç½®ï¼æ°å¢å
¬å¸åªéå ä¸è¡ï¼logo å¾çæ¾ public/logos/ ç®å½ */ |
| | | const companyConfigs: Record<string, { favicon: string; logo: string; name: string }> = { |
| | | TEST: { name: 'æµè¯å
¬å¸', logo: '/logos/TEST.png', favicon: '/logos/TEST.ico' }, |
| | | }; |
| | | import { companies } from "./company.config"; |
| | | |
| | | export default defineConfig(async ({ mode }) => { |
| | | const env = loadEnv(mode, process.cwd(), 'VITE_'); |
| | | const companyKey = env.VITE_COMPANY || ''; |
| | | const company = companyConfigs[companyKey]; |
| | | const company = companies[companyKey]; |
| | | const appLogo = company?.logo ?? ''; |
| | | const appFavicon = company?.favicon ?? ''; |
| | | console.log('[company]', companyKey, '-> logo:', appLogo, 'favicon:', appFavicon); |
| | | const appApiUrl = company?.apiUrl ?? ''; |
| | | const isDev = mode === 'development'; |
| | | console.log('[company]', companyKey, '-> name:', company?.name, 'logo:', appLogo, 'api:', appApiUrl || '(from env)'); |
| | | |
| | | const defineVars: Record<string, string> = { |
| | | __APP_LOGO__: JSON.stringify(appLogo), |
| | | __APP_FAVICON__: JSON.stringify(appFavicon), |
| | | }; |
| | | if (!isDev && appApiUrl) { |
| | | defineVars['import.meta.env.VITE_GLOB_API_URL'] = JSON.stringify(appApiUrl); |
| | | defineVars['import.meta.env.VITE_BASE_URL'] = JSON.stringify(new URL(appApiUrl).origin); |
| | | } |
| | | |
| | | return { |
| | | application: {}, |
| | | vite: { |
| | | define: { |
| | | __APP_LOGO__: JSON.stringify(appLogo), |
| | | __APP_FAVICON__: JSON.stringify(appFavicon), |
| | | }, |
| | | define: defineVars, |
| | | resolve: { |
| | | alias: { |
| | | '@vue-office/pdf': resolve(__dirname, 'node_modules/@vue-office/pdf/lib/v3/vue-office-pdf.mjs'), |