feat: 导出产品合同 .docx

perf/export-docx
Lei OT 10 months ago
parent 6ecb8908f9
commit cb0f519adf

@ -35,6 +35,13 @@ export const getAgencyProductsAction = async (param) => {
return errcode !== 0 ? { agency: {}, products: [] } : result; return errcode !== 0 ? { agency: {}, products: [] } : result;
}; };
export const getAgencyAllExtrasAction = async (param) => {
const _param = { ...param, use_year: String(param.use_year || '').replace('all', ''), };
const { errcode, result } = await fetchJSON(`${HT_HOST}/Service_BaseInfoWeb/agency_products_extras`, _param);
const extrasMapped = result.reduce((acc, curr) => ({...acc, [curr.product_id]: curr.extras}), {});
return errcode !== 0 ? {} : extrasMapped;
}
/** /**
* @param {object} body { id, travel_agency_id, extras: [{id, title, code}] } * @param {object} body { id, travel_agency_id, extras: [{id, title, code}] }
*/ */

@ -162,6 +162,13 @@ export const sortArrayByOrder = (items, keyName, keyOrder) => {
return keyOrder.indexOf(a[keyName]) - keyOrder.indexOf(b[keyName]); return keyOrder.indexOf(a[keyName]) - keyOrder.indexOf(b[keyName]);
}); });
}; };
export function unique(arr) {
const x = new Set(arr);
return [...x];
}
export const uniqWith = (arr, fn) => arr.filter((element, index) => arr.findIndex((step) => fn(element, step)) === index);
/** /**
* 合并Object, 递归地 * 合并Object, 递归地
*/ */

@ -172,7 +172,7 @@ const Audit = ({ ...props }) => {
const [loading, setLoading] = useProductsStore(state => [state.loading, state.setLoading]); const [loading, setLoading] = useProductsStore(state => [state.loading, state.setLoading]);
const { travelAgencyId } = usingStorage(); const { travelAgencyId } = usingStorage();
const handleGetAgencyProducts = ({pick_year, pick_agency, pick_state}={}) => { const handleGetAgencyProducts = async ({pick_year, pick_agency, pick_state}={}) => {
const year = pick_year || use_year || dayjs().year(); const year = pick_year || use_year || dayjs().year();
const agency = pick_agency || travel_agency_id || travelAgencyId; const agency = pick_agency || travel_agency_id || travelAgencyId;
const state = pick_state ?? audit_state; const state = pick_state ?? audit_state;

@ -20,7 +20,7 @@ function Detail() {
const [loading, setLoading] = useProductsStore((state) => [state.loading, state.setLoading]); const [loading, setLoading] = useProductsStore((state) => [state.loading, state.setLoading]);
const { travelAgencyId } = usingStorage(); const { travelAgencyId } = usingStorage();
const handleGetAgencyProducts = ({ pick_year, pick_agency, pick_state } = {}) => { const handleGetAgencyProducts = async ({ pick_year, pick_agency, pick_state } = {}) => {
const year = pick_year || use_year || switchParams.use_year || dayjs().year(); const year = pick_year || use_year || switchParams.use_year || dayjs().year();
const agency = pick_agency || travel_agency_id || travelAgencyId; const agency = pick_agency || travel_agency_id || travelAgencyId;
const state = pick_state ?? audit_state; const state = pick_state ?? audit_state;

@ -4,7 +4,7 @@ import { App, Button, Divider, Popconfirm, Select } from 'antd';
import { ReloadOutlined } from '@ant-design/icons'; import { ReloadOutlined } from '@ant-design/icons';
import { useProductsAuditStatesMapVal } from '@/hooks/useProductsSets'; import { useProductsAuditStatesMapVal } from '@/hooks/useProductsSets';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useProductsStore, { postAgencyProductsAuditAction, postAgencyAuditAction } from '@/stores/Products/Index'; import useProductsStore, { postAgencyProductsAuditAction, postAgencyAuditAction, getAgencyAllExtrasAction } from '@/stores/Products/Index';
import { isEmpty, objectMapper } from '@/utils/commons'; import { isEmpty, objectMapper } from '@/utils/commons';
import useAuthStore from '@/stores/Auth'; import useAuthStore from '@/stores/Auth';
import RequireAuth from '@/components/RequireAuth'; import RequireAuth from '@/components/RequireAuth';
@ -30,7 +30,7 @@ const Header = ({ refresh, ...props }) => {
const [activeAgency, setActiveAgency] = useProductsStore((state) => [state.activeAgency, state.setActiveAgency]); const [activeAgency, setActiveAgency] = useProductsStore((state) => [state.activeAgency, state.setActiveAgency]);
const [switchParams, setSwitchParams] = useProductsStore((state) => [state.switchParams, state.setSwitchParams]); const [switchParams, setSwitchParams] = useProductsStore((state) => [state.switchParams, state.setSwitchParams]);
// const [activeAgencyState] = useProductsStore((state) => [state.activeAgencyState]); // const [activeAgencyState] = useProductsStore((state) => [state.activeAgencyState]);
// const [agencyProducts] = useProductsStore((state) => [state.agencyProducts]); const [agencyProducts] = useProductsStore((state) => [state.agencyProducts]);
const stateMapVal = useProductsAuditStatesMapVal(); const stateMapVal = useProductsAuditStatesMapVal();
const { message, notification } = App.useApp(); const { message, notification } = App.useApp();
const navigate = useNavigate(); const navigate = useNavigate();
@ -129,12 +129,15 @@ const Header = ({ refresh, ...props }) => {
}; };
const handleDownload = () => { const handleDownload = async () => {
// await refresh();
const agencyExtras = await getAgencyAllExtrasAction(switchParams);
const documentCreator = new AgencyContract(); const documentCreator = new AgencyContract();
const doc = documentCreator.create([]); const doc = documentCreator.create([switchParams, activeAgency, agencyProducts, agencyExtras]);
const _d = dayjs().format('YYYYMMDD_HH.mm.ss.SSS'); // Date.now().toString(32)
Packer.toBlob(doc).then(blob => { Packer.toBlob(doc).then(blob => {
saveAs(blob, `${pickYear}地接合同-${Date.now().toString(32)}.docx`); saveAs(blob, `${activeAgency.travel_agency_name}${pickYear}年地接合同-${_d}.docx`);
}); });
}; };

@ -4,9 +4,7 @@ import {
Header, Header,
Footer, Footer,
HeadingLevel, HeadingLevel,
Packer,
Paragraph, Paragraph,
TabStopPosition,
TabStopType, TabStopType,
Table, Table,
TableCell, TableCell,
@ -16,7 +14,118 @@ import {
NumberFormat, NumberFormat,
PageNumber, PageNumber,
WidthType, WidthType,
LevelFormat,
Tab,
PositionalTab,
PositionalTabAlignment,
PositionalTabRelativeTo,
PositionalTabLeader,
} from 'docx'; } from 'docx';
import dayjs from 'dayjs';
import { cloneDeep, groupBy, isEmpty, unique, uniqWith } from '@/utils/commons';
// Shoulder Season ; peak season
const isFullYearOrLonger = (year, startDate, endDate) => {
// Parse the dates
const start = dayjs(startDate, 'YYYY-MM-DD');
const end = dayjs(endDate, 'YYYY-MM-DD');
// Create the start and end dates for the year
const yearStart = dayjs(`${year}-01-01`, 'YYYY-MM-DD');
const yearEnd = dayjs(`${year}-12-31`, 'YYYY-MM-DD');
// Check if start is '01-01' and end is '12-31' and the year matches
const isFullYear = start.isSame(yearStart, 'day') && end.isSame(yearEnd, 'day');
// Check if the range is longer than a year
const isLongerThanYear = end.diff(start, 'year') >= 1;
return isFullYear || isLongerThanYear;
};
const shortDate = (date) => dayjs(date).format('MM.DD');
/**
* 车辆人等配置
* 5, 7, 10, 10以上
*/
const chunkCarSets = (numbers) => {
return numbers.reduce(
(acc, number) => {
if (number < 5) {
acc.lt5.push(number);
} else if (number < 7) {
acc.lt7.push(number);
} else if (number < 10) {
acc.lt7.push(number);
} else {
acc.gt10.push(number);
}
return acc;
},
{ lt5: [], lt7: [], lt10: [], gt10: [] }
);
};
const chunkBy = (use_year, dataList = [], by = []) => {
const groupSize = dataList.reduce((a, c) => a.concat((c.quotation || []).reduce((aq, cq) => aq.concat([[cq.group_size_min, cq.group_size_max]]), [])), []);
const allSizeCut = groupBy(
uniqWith(groupSize, (a, b) => a[0] === b[0] && a[1] === b[1]),
(c) => c[0]
);
const sizeSets = Object.keys(allSizeCut).map((s1) => [Number(s1), Math.max(...allSizeCut[s1].map((c) => c[1]))]);
const sizeSetsByMax = uniqWith(sizeSets, (a, b) => a[1] === b[1]);
// const sizeSetsByMax = [[1,4], [5,6], [7, 9], [10, Infinity]]; // test:
const dataRoll = dataList.map((rowp, ii) => {
const quotation = rowp.quotation.map((quoteItem) => {
const matchRange = cloneDeep(sizeSetsByMax)
.reverse()
.find((ss) => quoteItem.group_size_min >= ss[0] && quoteItem.group_size_max <= ss[1]);
// const matchMax =
// matchRange ||
// cloneDeep(sizeSetsByMax)
// // .reverse()
// .find((ss) => quoteItem.group_size_max <= ss[1]);
// const matchMin =
// matchMax ||
// cloneDeep(sizeSetsByMax)
// // .reverse()
// .find((ss) => quoteItem.group_size_min >= ss[0]);
// const inSet = matchMin;
const inSet = matchRange;
return {
...quoteItem,
quote_season: isFullYearOrLonger(use_year, quoteItem.use_dates_start, quoteItem.use_dates_end) ? 'SS' : 'PS',
quote_size: inSet ? inSet.join('-') : '10-Infinity',
};
});
return { ...rowp, quotation };
});
const chunk = dataRoll.map((rowp) => {
const quote_chunk = groupBy(rowp.quotation, (quoteItem) => by.map((key) => quoteItem[key]).join('@'));
return { ...rowp, quote_chunk };
});
const dataSource = chunk.map((rowp, ri) => {
const thisChunk = Object.keys(rowp.quote_chunk).reduce((qc, ckey) => {
const ckeyArr = ckey.split('@');
if (isEmpty(qc[ckeyArr[0]])) {
qc[ckeyArr[0]] = ckeyArr[1] ? { [ckeyArr[1]]: rowp.quote_chunk[ckey] } : rowp.quote_chunk[ckey];
} else {
qc[ckeyArr[0]][ckeyArr[1]] = (qc[ckeyArr[0]][ckeyArr[1]] || []).concat(rowp.quote_chunk[ckey]);
}
return qc;
}, {});
return { ...rowp, quote_chunk: thisChunk };
});
const allquotation = chunk.reduce((a, c) => a.concat(c.quotation), []);
const SSRange = unique((allquotation || []).filter((q) => q.quote_season === 'SS').map((qr) => `${qr.use_dates_start}~${qr.use_dates_end}`));
const PSRange = unique((allquotation || []).filter((q) => q.quote_season === 'PS').map((qr) => `${qr.use_dates_start}~${qr.use_dates_end}`));
// console.log(chunk, dataSource, sizeSets, sizeSetsByMax);
return { chunk, dataSource, sizeSets: sizeSetsByMax, SSRange, PSRange };
};
const DOC_TITLE = '地接合同'; const DOC_TITLE = '地接合同';
const pageMargins = { const pageMargins = {
top: `15mm`, top: `15mm`,
@ -38,10 +147,18 @@ const tableBorderOne = {
}; };
export default class AgencyContract { export default class AgencyContract {
create([data]) { create([headerParams, activeAgency, agencyProducts, agencyExtras]) {
const { use_year } = headerParams;
const h1 = `${activeAgency.travel_agency_name}${use_year}${DOC_TITLE}`;
const yearStart = dayjs(`${use_year}-01-01`).format('YYYY.MM.DD');
const yearEnd = dayjs(`${use_year}-12-31`).format('YYYY.MM.DD');
// console.log(agencyExtras);
const document = new Document({ const document = new Document({
creator: 'China Highlights', creator: 'China Highlights',
title: DOC_TITLE, title: h1,
subject: '2025桂林海纳价格合同模板',
// description: '',
styles: { styles: {
paragraphStyles: [ paragraphStyles: [
{ {
@ -49,13 +166,31 @@ export default class AgencyContract {
name: 'Normal', name: 'Normal',
quickFormat: true, quickFormat: true,
run: { run: {
size: 20, size: 22,
font: { name: '宋体' }, font: { name: '宋体' },
color: '000000', color: '000000',
}, },
paragraph: { paragraph: {
spacing: { spacing: {
after: 0, after: 10,
},
},
},
{
id: 'Title',
name: 'Title',
basedOn: 'Normal',
next: 'Normal',
quickFormat: true,
run: {
size: 44,
font: { name: '宋体' },
color: '000000',
},
paragraph: {
spacing: {
before: 200,
after: 200,
}, },
}, },
}, },
@ -72,7 +207,8 @@ export default class AgencyContract {
}, },
paragraph: { paragraph: {
spacing: { spacing: {
after: 120, before: 200,
after: 200,
}, },
}, },
}, },
@ -83,17 +219,53 @@ export default class AgencyContract {
next: 'Normal', next: 'Normal',
quickFormat: true, quickFormat: true,
run: { run: {
size: 24, size: 28,
font: { name: '宋体' }, font: { name: '宋体' },
color: '000000', color: '000000',
}, },
paragraph: { paragraph: {
spacing: { spacing: {
before: 120, before: 120,
// after: 120, after: 120,
}, },
}, },
}, },
{
id: 'TableHeader',
name: 'Table Header',
basedOn: 'Normal',
next: 'Normal',
quickFormat: true,
run: {
size: 22,
font: { name: '宋体' },
color: '000000',
bold: true,
},
paragraph: {
spacing: {
before: 80,
after: 80,
},
},
},
// {
// id: "MySpectacularStyle",
// name: "My Spectacular Style",
// basedOn: "Heading1",
// next: "Heading1",
// quickFormat: true,
// run: {
// italics: true,
// color: "990000",
// },
// },
],
},
numbering: {
config: [
{ reference: 'products-type', levels: [{ level: 0, text: '%1、', format: LevelFormat.CHINESE_COUNTING }] },
{ reference: 'terms', levels: [{ level: 0, text: '%1.', format: LevelFormat.DECIMAL }] },
], ],
}, },
sections: [ sections: [
@ -109,7 +281,10 @@ export default class AgencyContract {
}, },
headers: { headers: {
default: new Header({ default: new Header({
children: [this.createPageHeaderText(`${new Date().toLocaleString()}系统生成`)], children: [
this.createPageHeaderText(`Email: xeniayou@hainatravel.com`, { italics: false }),
this.createPageHeaderText(`https://www.chinahighlights.com`, { italics: false }),
],
}), }),
}, },
footers: { footers: {
@ -137,40 +312,633 @@ export default class AgencyContract {
size: 20, size: 20,
}), }),
], ],
}) }),
], ],
}), }),
}, },
children: [
// new Paragraph({
// text: h1,
// heading: HeadingLevel.HEADING_1,
// alignment: 'center',
// }),
this.createTitle(h1),
this.createSubHeading(`价格有效期: ${yearStart}-${yearEnd}`, { alignment: AlignmentType.CENTER }),
...(isEmpty(agencyProducts['6'])
? []
: [this.createSubHeading(`综费`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_6(use_year, agencyProducts['6'])]),
...(isEmpty(agencyProducts['B'])
? []
: [this.createSubHeading(`超公里`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_B(use_year, agencyProducts['B'])]),
...(isEmpty(agencyProducts['J'])
? []
: [this.createSubHeading(`车费`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_J(use_year, agencyProducts['J'])]),
...(isEmpty(agencyProducts['Q'])
? []
: [this.createSubHeading(`导游`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_6(use_year, agencyProducts['Q'])]),
// this.createTable_Q(use_year, agencyProducts['Q']),
...(isEmpty(agencyProducts['7'])
? []
: [this.createSubHeading(`景点`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_7(use_year, agencyProducts['7'], agencyExtras)]),
...(isEmpty(agencyProducts['R'])
? []
: [this.createSubHeading(`餐费`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_R(use_year, agencyProducts['R'], agencyExtras)]),
...(isEmpty(agencyProducts['D'])
? []
: [this.createSubHeading(`包价线路`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_J(use_year, agencyProducts['D'])]),
this.createSubHeading(`公司银行账户`, { numbering: { reference: 'products-type', level: 0 } }),
this.createInfo_Bank(),
this.createSubHeading(`联系资料`, { numbering: { reference: 'products-type', level: 0 } }),
this.createInfo_Contact(),
this.createSubHeading(`补充条款`, { numbering: { reference: 'products-type', level: 0 } }),
...this.createInfo_Terms(),
],
},
],
});
return document;
}
/**
* 综费
*/
createTable_6(use_year, dataList, style = {}) {
const { chunk, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season']);
return new Table({
borders: tableBorderNone,
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
rows: [
new TableRow({
children: [this.createTableHeader('项目', 30), this.createTableHeader(['平季', ...SSRange], 35), this.createTableHeader(['旺季', ...PSRange], 35)],
}),
...chunk.map((row, ri) => this.createDetailRowSeason2(row, ri)).reduce((a, c) => a.concat(c), []),
this.createTable_Memo(3),
],
});
}
/**
* 超公里
*/
createTable_B(use_year, dataList) {
const { chunk: dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_size']);
const infoCol = (rowp) =>
new TableCell({
borders: tableBorderOne,
verticalAlign: AlignmentType.CENTER,
children: [ children: [
new Paragraph({ new Paragraph({
text: DOC_TITLE, text: `${rowp?.info?.km}`,
heading: HeadingLevel.HEADING_1, alignment: AlignmentType.CENTER,
alignment: 'center',
}), }),
this.createSubHeading(`副标题`),
], ],
});
return new Table({
borders: tableBorderNone,
width: {
size: 100,
type: WidthType.PERCENTAGE,
}, },
rows: [
new TableRow({
children: [
this.createTableHeader('超公里项目', 20),
this.createTableHeader('往返公里数', 20),
this.createTableHeader(['全年', ...SSRange, ...PSRange], 60, { columnSpan: sizeSets.length }),
],
}),
new TableRow({
children: [
new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }),
new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }),
...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })),
],
}),
...dataSource.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, { infoCol })).reduce((a, c) => a.concat(c), []),
this.createTable_Memo(2 + sizeSets.length),
], ],
}); });
}
return document; /**
* 车费
*/
createTable_J(use_year, dataList) {
const { chunk, dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season', 'quote_size']);
return new Table({
borders: tableBorderNone,
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
rows: [
new TableRow({
children: [
this.createTableHeader('项目', 20),
this.createTableHeader(['平季', ...SSRange], 40, { columnSpan: sizeSets.length }),
this.createTableHeader(['旺季', ...PSRange], 40, { columnSpan: sizeSets.length }),
],
}),
new TableRow({
children: [
new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }),
...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })),
...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })),
],
}),
...dataSource.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, { withSeason: true })).reduce((a, c) => a.concat(c), []),
this.createTable_Memo(1 + sizeSets.length * 2),
],
});
} }
/**
* 导游
* @ignore
* @deprecated
*/
createTable_Q(use_year, dataList) {}
/**
* 景点
*/
createTable_7(use_year, dataList, agencyExtras) {
const withExtras = dataList.map((row) => ({ ...row, extras: agencyExtras[row.info.id] || [] }));
const { chunk, sizeSets, SSRange, PSRange } = chunkBy(use_year, withExtras, ['quote_season']);
const extraCol = (rowp, i) => {
return new TableCell({
borders: tableBorderOne,
verticalAlign: AlignmentType.CENTER,
children: [
...(rowp.extras || []).reduce((ac, extra) => {
const allSize = unique(extra.quotation.map((eq) => `${eq.group_size_min}-${eq.group_size_max}`));
const allDates = unique(extra.quotation.map((eq) => `${eq.use_dates_start}-${eq.use_dates_end}`));
// const compactBy = allSize.length > 1 ? 'size' : allDates.length > 1 ? 'dates' : 'all';
const compactBy = allSize.length < allDates.length ? 'size' : 'dates';
const compactQuotation = groupBy(
extra.quotation,
(ExtraQuoteItem) =>
`${ExtraQuoteItem.adult_cost}@${ExtraQuoteItem.unit_id}.` +
(compactBy === 'size' ? `[${ExtraQuoteItem.group_size_min}.${ExtraQuoteItem.group_size_max}]` : `${ExtraQuoteItem.use_dates_start},${ExtraQuoteItem.use_dates_end}`)
);
// console.log(compactQuotation);
const thisE = new Paragraph({
children: [new TextRun({ text: `${extra.info.product_title}`, bold: true })],
});
const thisP = Object.entries(compactQuotation).map(([key, [firstQ, ...others]]) => {
const date1 = shortDate(firstQ.use_dates_start);
const date2 = shortDate(firstQ.use_dates_end);
const othersDates = others.map((oq) => `${shortDate(oq.use_dates_start)}~${shortDate(oq.use_dates_end)}`).join(', ');
const otherStr = isEmpty(othersDates) ? '' : `, ${othersDates}`;
return new Paragraph({
children: [
// new TextRun({ text: `${extra.info.product_title} ${firstQ.unit_name}`, bold: true }),
new TextRun({
text:
`${firstQ.adult_cost}, ${firstQ.group_size_min}-${firstQ.group_size_max}人, ${firstQ.unit_name}` +
// + (`, ${firstQ.group_size_min}-${firstQ.group_size_max}`)
(compactBy !== 'dates' ? `, ${date1}~${date2}${otherStr}` : ''),
}),
],
});
});
return ac.concat([thisE, ...thisP]);
// return new Paragraph({ text: `${extra.info.product_title} ${extra.quotation?.[0]?.adult_cost || ''} ${extra.quotation?.[0]?.unit_name || ''}` });
}, []),
],
});
};
return new Table({
borders: tableBorderNone,
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
rows: [
new TableRow({
children: [
this.createTableHeader('景点', 25),
this.createTableHeader(['平季', ...SSRange], 25),
this.createTableHeader(['旺季', ...PSRange], 25),
this.createTableHeader('附加费用', 25),
],
}),
...chunk.map((row, ri) => this.createDetailRowSeason2(row, ri, { extraCol })).reduce((a, c) => a.concat(c), []),
this.createTable_Memo(1 + sizeSets.length * 2 + 1),
],
});
}
/**
* 餐费
*/
createTable_R(use_year, dataList, agencyExtras) {
const withExtras = dataList.map((row) => ({ ...row, extras: agencyExtras[row.info.id] || [] }));
const { chunk, sizeSets, SSRange, PSRange } = chunkBy(use_year, withExtras, ['quote_size']);
const extraCol = (rowp, i) =>
new TableCell({
borders: tableBorderOne,
verticalAlign: AlignmentType.CENTER,
children: [
...(rowp.extras || []).map(
(extra) => new Paragraph({ text: `${extra.info.product_title} ${extra.quotation?.[0]?.adult_cost || ''} ${extra.quotation?.[0]?.unit_name || ''}` })
),
// new Paragraph({ text: '' })
],
});
return new Table({
borders: tableBorderNone,
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
rows: [
new TableRow({
children: [
this.createTableHeader('社会餐厅午餐/晚餐', 25),
this.createTableHeader(['价格', ...SSRange, ...PSRange], 50, { columnSpan: sizeSets.length }),
this.createTableHeader('司陪餐补', 25),
],
}),
new TableRow({
children: [
new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }),
...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })),
// new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, rowSpan: dataList.length+1, children: [new Paragraph({ text: '' })] }),
new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }),
],
}),
...chunk.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, { withSize: false, extraCol })).reduce((a, c) => a.concat(c), []),
// ...dataSource.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, true)).reduce((a, c) => a.concat(c), []),
this.createTable_Memo(1 + sizeSets.length + 1),
],
});
}
/**
* 包价线路
* @deprecated
*/
createTable_D(use_year, dataList) {
const { chunk, dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season', 'quote_size']);
return new Table({
borders: tableBorderNone,
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
rows: [
new TableRow({
children: [
this.createTableHeader('项目', 30),
this.createTableHeader(['平季', ...SSRange], 35, { columnSpan: sizeSets.length }),
this.createTableHeader(['旺季', ...PSRange], 35, { columnSpan: sizeSets.length }),
],
}),
new TableRow({
children: [
new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }),
...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })),
...sizeSets.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: ss.join('-') })] })),
],
}),
...dataSource.map((row, ri) => this.createDetailRowSize(sizeSets, row, ri, { withSeason: true })).reduce((a, c) => a.concat(c), []),
this.createTable_Memo(1 + sizeSets.length * 2),
],
});
}
createDetailRowSize(sizeSets, rowp, ii, { withSeason = false, withSize = true, infoCol = () => {}, extraCol = () => {} } = {}) {
const sizeCol = sizeSets.map((ss) => ss.join('-'));
return [
new TableRow({
children: [
// new TableCell({
// borders: tableBorderOne,
// rowSpan: 3,
// verticalAlign: AlignmentType.CENTER,
// children: [new Paragraph(String(ii + 1))],
// }),
new TableCell({
borders: tableBorderOne,
verticalAlign: AlignmentType.CENTER,
children: [
new Paragraph({
text: `${rowp?.info?.product_title}`,
alignment: AlignmentType.LEFT,
}),
],
}),
infoCol(rowp),
...sizeCol.map(
(ss) =>
new TableCell({
borders: tableBorderOne,
width: { size: 2000, type: WidthType.DXA },
verticalAlign: AlignmentType.CENTER,
children: [
...((withSeason ? rowp.quote_chunk?.['SS']?.[ss] : rowp.quote_chunk[ss]) || []).map(
(quoteItem, ii) =>
new Paragraph({
alignment: AlignmentType.LEFT,
text: isEmpty(quoteItem)
? ''
: withSize
? `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}`
: `${quoteItem.adult_cost}`,
})
),
// new Paragraph({ text: '-'+ii })
],
})
),
...(withSeason
? sizeCol.map(
(ss) =>
new TableCell({
borders: tableBorderOne,
width: { size: 2000, type: WidthType.DXA },
verticalAlign: AlignmentType.CENTER,
children: [
...(rowp.quote_chunk?.['PS']?.[ss] || []).map(
(quoteItem, ii) =>
new Paragraph({
alignment: AlignmentType.LEFT,
// text: `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}${quoteItem.unit_name}`,
text: withSize
? `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}`
: `${quoteItem.adult_cost}`,
})
),
],
})
)
: []),
extraCol(rowp, ii),
],
}),
];
}
createDetailRowSeason2(rowp, ii, { extraCol = () => {} } = {}) {
return [
new TableRow({
children: [
// new TableCell({
// borders: tableBorderOne,
// rowSpan: 3,
// verticalAlign: AlignmentType.CENTER,
// children: [new Paragraph(String(ii + 1))],
// }),
new TableCell({
borders: tableBorderOne,
verticalAlign: AlignmentType.CENTER,
children: [
new Paragraph({
text: `${rowp?.info?.product_title}`,
alignment: AlignmentType.LEFT,
}),
],
}),
new TableCell({
borders: tableBorderOne,
width: { size: 2000, type: WidthType.DXA },
verticalAlign: AlignmentType.CENTER,
children: [
...(rowp.quote_chunk['SS'] || []).map(
(quoteItem, ii) =>
new Paragraph({
alignment: AlignmentType.LEFT,
text: `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}`,
})
),
],
}),
new TableCell({
borders: tableBorderOne,
width: { size: 2000, type: WidthType.DXA },
verticalAlign: AlignmentType.CENTER,
children: [
...(rowp.quote_chunk['PS'] || []).map(
(quoteItem, ii) =>
new Paragraph({
alignment: AlignmentType.LEFT,
text: `${quoteItem.adult_cost}, ${quoteItem.group_size_min}-${quoteItem.group_size_max}人,${quoteItem.unit_name}`,
})
),
],
}),
extraCol(rowp, ii),
],
}),
];
}
createTable_Memo(columnSpan) {
return new TableRow({
children: [
new TableCell({
borders: tableBorderOne,
// verticalAlign: AlignmentType.CENTER,
columnSpan,
children: [new Paragraph(`备注: `), new Paragraph(``)],
}),
],
});
}
createInfo_Bank() {
const Col1 = (label, opt = {}) =>
new TableCell({
borders: tableBorderOne,
width: { size: 2000, type: WidthType.DXA },
// verticalAlign: AlignmentType.CENTER,
children: [new Paragraph(label)],
...opt,
});
return new Table({
borders: tableBorderNone,
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
rows: [
new TableRow({
children: [Col1('帐户:'), new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] })],
}),
new TableRow({
children: [Col1('银行:'), new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] })],
}),
new TableRow({
children: [Col1('帐号:'), new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] })],
}),
new TableRow({
children: [Col1('结账方式:'), new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] })],
}),
],
});
}
createInfo_Contact() {
const Col1 = (label, opt = {}) =>
new TableCell({
borders: tableBorderOne,
width: { size: 2000, type: WidthType.DXA },
// verticalAlign: AlignmentType.CENTER,
children: [new Paragraph(label)],
...opt,
});
return new Table({
borders: tableBorderNone,
width: {
size: 100,
type: WidthType.PERCENTAGE,
},
rows: [
new TableRow({
children: [this.createTableHeader('联系人', 25), this.createTableHeader('电话', 25), this.createTableHeader('微信', 25), this.createTableHeader('手机', 25)],
}),
new TableRow({
children: [Col1('经理:'), ...[null, null, null].map((col) => new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] }))],
}),
new TableRow({
children: [Col1('计调:'), ...[null, null, null].map((col) => new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] }))],
}),
new TableRow({
children: [Col1('财务(发票):'), ...[null, null, null].map((col) => new TableCell({ borders: tableBorderOne, children: [new Paragraph(``)] }))],
}),
],
});
}
createInfo_Terms() {
const subHeader = (text) =>
new Paragraph({
alignment: AlignmentType.CENTER,
outlineLevel: 1,
spacing: { after: 200, before: 200 },
style: 'TableHeader',
children: [new TextRun({ text: `${text}`, bold: true, size: 28 })],
});
const numberingContent = ([abstract, ...content], instance) => [
new Paragraph({ text: abstract, indent: { firstLine: `2pc` } }),
...content.map(
([itemTitle, ...itemList]) =>
new Paragraph({
text: itemTitle,
numbering: { reference: 'terms', level: 0, instance },
children: itemList.map((li) => new TextRun({ text: li, break: 1 })),
})
),
];
return [
subHeader('中华游专利产品接待权标准'),
...numberingContent(
[
'中华游是注重特色新产品开发的品牌,并希望通过开发新产品丰富中华游的网前线路,吸引更多的客户与中华游订购中国之旅。为了更好的鼓励各个地接社积极开发新产品、新线路,特制定中华游专利产品接待权标准,详情如下:',
['新产品,新线路定义及专利期', '新产品,新线路非市场上现成的新产品,新体验,而是包含了独家设计或独家资源的产品和体验'],
['中华游和地接社联合设计研发的新产品', '由中华游发起立意和产品框架', '地接社协助填充具体内容并落地', '专利期6个月'],
['地接社自主设计研发并落地(中华游或做细微调整)', '如:北京“民生探索”', '专利期:永久 (除非地接社已无法掌握产品或资源的独家性)'],
],
1
),
subHeader('关于价格变更'),
...numberingContent(
[
'中华游与各地接社签订年度地接协议后,将不认同中途涨价的要求。但考虑在实际合作中存在的不可控因素,为维护双方共同利益,两种价格变更情况处理方式如下',
[
'常规价格变更:',
'一般景区和酒店会提前3个月通知价格变更事宜要求地接社及时用聊天工具或邮件形式通知中华游产品采购,并以收到相同形式确认回执为准,否则中华游将有权拒付额外增加的一切费用。额外增加费用的承担规则为已确认团队计划保持原价,新价格仅适用于确认回复日期起的新发送团队计划。',
],
[
'临时价格变更:',
'一般属于政府行为或景区强制要求,如不立即交款则无法参观等紧急情况。因此种情况产生的额外费用要求第一时间通知中华游产品采购,并秉承维护双方共同利益与长期合作,共同协商友好解决方案。',
],
],
2
),
new Paragraph({
text: '签名盖章',
spacing: { before: 600, after: 200 },
children: [
new TextRun({ break: 2, children: ['甲方:', new Tab(), '乙方:桂林海纳国际旅行社有限公司'] }),
new TextRun({ break: 1, children: ['负责人签字盖章', new Tab(), '负责人签字盖章'] }),
new TextRun({ break: 3, children: ['联系人: ', new Tab(), '联系人:游佳佳'] }),
new TextRun({ break: 1, children: ['电话: ', new Tab(), '电话13507831547'] }),
new TextRun({ break: 1, children: ['电子邮箱: ', new Tab(), '电子邮箱xeniayou@hainatravel.com'] }),
new TextRun({ break: 1, children: [' 年 月 日', new Tab(), ' 年 月 日'] }),
],
tabStops: [{ type: TabStopType.LEFT, position: 4800 }],
}),
// new Paragraph({
// tabStops: [{ type: TabStopType.RIGHT, position: 1000 }],
// children: [
// new TextRun({
// children: [
// '\t',
// '\t\t',
// '\t\t',
// new PositionalTab({
// alignment: PositionalTabAlignment.CENTER,
// relativeTo: PositionalTabRelativeTo.MARGIN,
// leader: PositionalTabLeader.NONE,
// }),
// // new Tab(),
// '\t',
// '\t\t',
// '\t\t',
// ],
// }),
// ],
// }),
];
}
createTableHeader(text, w = null, opt = {}) {
const textP = typeof text === 'string' ? [text] : text;
return new TableCell({
borders: tableBorderOne,
width: { size: w || `8mm`, type: w ? WidthType.PERCENTAGE : WidthType.AUTO },
verticalAlign: AlignmentType.CENTER,
children: textP.map((t) => new Paragraph({ text: t, alignment: AlignmentType.CENTER, style: 'TableHeader' })),
...opt,
});
}
createHeading(text) { createHeading(text) {
return new Paragraph({ return new Paragraph({
text: text, text: text,
heading: HeadingLevel.HEADING_1, heading: HeadingLevel.HEADING_1,
thematicBreak: true, // thematicBreak: true,
alignment: AlignmentType.CENTER,
}); });
} }
createSubHeading(text) { createTitle(text) {
return new Paragraph({ return new Paragraph({
text: text, text: text,
heading: HeadingLevel.HEADING_2, heading: HeadingLevel.TITLE,
// thematicBreak: true,
alignment: AlignmentType.CENTER,
});
}
createSubHeading(text, style = {}) {
return new Paragraph({
text: text,
heading: HeadingLevel.HEADING_1,
...style,
}); });
} }
createPageHeaderText(text) { createPageHeaderText(text, style = {}) {
return new Paragraph({ return new Paragraph({
alignment: AlignmentType.RIGHT, alignment: AlignmentType.RIGHT,
children: [ children: [
@ -178,6 +946,7 @@ export default class AgencyContract {
text: text, text: text,
italics: true, italics: true,
size: 20, size: 20,
...style,
}), }),
], ],
}); });

Loading…
Cancel
Save