diff --git a/src/views/products/Print/AgencyContract.jsx b/src/views/products/Print/AgencyContract.jsx index 3aaa855..3ce91a0 100644 --- a/src/views/products/Print/AgencyContract.jsx +++ b/src/views/products/Print/AgencyContract.jsx @@ -22,7 +22,7 @@ import { PositionalTabLeader, } from 'docx'; import dayjs from 'dayjs'; -import { cloneDeep, groupBy, isEmpty, unique, uniqWith } from '@/utils/commons'; +import { cloneDeep, groupBy, isEmpty, isNotEmpty, sortBy, unique, uniqWith } from '@/utils/commons'; // Shoulder Season 平季; peak season 旺季 const isFullYearOrLonger = (year, startDate, endDate) => { @@ -66,69 +66,131 @@ const chunkCarSets = (numbers) => { { lt5: [], lt7: [], lt10: [], gt10: [] } ); }; +const uniqueBySub = (arr) => + arr.filter((subArr1, _, self) => { + return !self.some((subArr2) => { + if (subArr1 === subArr2) return false; // don't compare a subarray with itself + const set1 = new Set(subArr1); + const set2 = new Set(subArr2); + // check if subArr1 is a subset of subArr2 + return [...set1].every((value) => set2.has(value)); + }); + }); +const isFlatUnique = (bigArray) => { + let flattenedArray = bigArray.flat(); + return flattenedArray.every((i) => { + let count = flattenedArray.filter((j) => j === i).length; + return count === 1; + }); +}; 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] - ); - // 按max最大值 cut分组 - 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: 固定车辆座次 - // 按max最小值 cut分组 - // const sizeSets = Object.keys(allSizeCut).map((s1) => [Number(s1), Math.min(...allSizeCut[s1].map((c) => c[1]))]); - // const sizeSetsByMax = uniqWith(sizeSets, (a, b) => a[1] === b[1]); - - const dataRoll = dataList.map((rowp, ii) => { + const dataRollSS = 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 inSet = matchRange; - // 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; 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 }; + 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 }; + // console.log('\ndataRollSS', dataRollSS); + // console.log('\n\n', dataList[0].info.product_type_id, dataRollSS); + + // 人等分组只取平季, 因为产品只一行 + const allQuotesSS = dataRollSS.reduce((acc, rowp) => acc.concat(rowp.quotation.filter(q => q.quote_season === 'SS')), []); + const allQuotesPS = dataRollSS.reduce((acc, rowp) => acc.concat(rowp.quotation.filter(q => q.quote_season === 'PS')), []); + const allQuotesSSS = isEmpty(allQuotesSS) ? allQuotesPS : allQuotesSS; + // console.log('allQuotesSSS', allQuotesSS, '\n', allQuotesPS, '\n', allQuotesSSS); + + // const groupSizeSS = (allQuotesSS).reduce((aq, cq) => aq.concat([[cq.group_size_min, cq.group_size_max]]), []); + const PGroupSizeSS = (allQuotesSSS).reduce((aq, cq) => { + aq[cq.WPI_SN] = aq[cq.WPI_SN] || []; + aq[cq.WPI_SN].push(cq.group_size_min); + aq[cq.WPI_SN] = unique(aq[cq.WPI_SN]); + return aq; + }, {}); + // console.log('PGroupSizeSS', structuredClone(PGroupSizeSS)); + const _SSMinSet = uniqWith(Object.values(PGroupSizeSS), (a, b) => a.join(',') === b.join(',')); + // console.log('SSMinSet', _SSMinSet); + const uSSsizeSetArr = uniqueBySub(_SSMinSet); + // console.log('uSSsizeSetArr', uSSsizeSetArr); + for (const key in PGroupSizeSS) { + if (Object.prototype.hasOwnProperty.call(PGroupSizeSS, key)) { + const element = PGroupSizeSS[key]; + const findSet = uSSsizeSetArr.find(minCut => element.every(v => minCut.includes(v))); + PGroupSizeSS[key] = findSet; + } + } + // console.log('PGroupSizeSS aaa', PGroupSizeSS); + const [SSsizeSets, PSsizeSets] = [uSSsizeSetArr, []].map((arr) => { + const _arr = structuredClone(arr); + const arrSets = _arr.map(keyMins => keyMins.reduce((acc, curr, idx, minsArr) => { + const _max = idx === minsArr.length - 1 ? Infinity : Number(minsArr[idx + 1])-1; + acc.push([Number(curr), _max]); + return acc; + }, [])); + return arrSets; }); - const dataSource = chunk.map((rowp, ri) => { - const thisChunk = Object.keys(rowp.quote_chunk).reduce((qc, ckey) => { + // console.log('PGroupSizeSS fff', SSsizeSets); + const compactSizeSets = { SSsizeSetKey: uSSsizeSetArr.map((s) => s.join(',')).filter(isNotEmpty), sizeSets: SSsizeSets }; + // console.log(compactSizeSets); + const chunkSS = structuredClone(dataRollSS).map(rowp => { + // console.log(rowp.info.id, rowp.info.product_title); + + const pkey = (PGroupSizeSS[rowp.info.id] || []).join(',') || compactSizeSets.SSsizeSetKey[0]; // todo: + // console.log('pkey', pkey); + + const thisRange = (PGroupSizeSS[rowp.info.id] || []).reduce((acc, curr, idx, minsArr) => { + const _max = idx === minsArr.length - 1 ? Infinity : Number(minsArr[idx + 1])-1; + acc.push([Number(curr), _max]); + return acc; + }, []); + const _quotation = rowp.quotation.map((quoteItem) => { + const ssSets = isEmpty(thisRange) ? SSsizeSets[0] : structuredClone(thisRange).reverse(); + // if (isEmpty(ssSets)) { + // console.group('quotation item'); + // console.log(rowp.info.id, rowp.info.product_title, rowp); + // console.log(ssSets); + // console.log([quoteItem.group_size_min, quoteItem.group_size_max]); + // console.groupEnd(); + // } + // console.group('quotation item'); + // console.log(ssSets); + // console.log([quoteItem.group_size_min, quoteItem.group_size_max]); + const matchRange = (ssSets).find((ss) => quoteItem.group_size_min >= ss[0] && quoteItem.group_size_max <= ss[1]); + const findEnd = matchRange || ssSets.find((ss) => quoteItem.group_size_max > ss[0] && quoteItem.group_size_max <= ss[1] && ss[1] !== Infinity); + const findStart = findEnd || ssSets.find((ss) => quoteItem.group_size_min >= ss[0]); + const finalRange = findStart; + // console.log('find mmm', matchRange, findEnd, findStart, finalRange); + // console.log('matchRange ', 'find', [quoteItem.group_size_min, quoteItem.group_size_max], 'matched', finalRange); + // console.groupEnd(); + quoteItem.quote_size = finalRange.join('-'); + return quoteItem; + }); + const quote_chunk_flat = groupBy(_quotation, (quoteItem2) => by.map((key) => quoteItem2[key]).join('@')); + const quote_chunk = Object.keys(quote_chunk_flat).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]; + qc[ckeyArr[0]] = ckeyArr[1] ? { [ckeyArr[1]]: quote_chunk_flat[ckey] } : quote_chunk_flat[ckey]; } else { - qc[ckeyArr[0]][ckeyArr[1]] = (qc[ckeyArr[0]][ckeyArr[1]] || []).concat(rowp.quote_chunk[ckey]); + qc[ckeyArr[0]][ckeyArr[1]] = (qc[ckeyArr[0]][ckeyArr[1]] || []).concat(quote_chunk_flat[ckey]); } return qc; }, {}); - - return { ...rowp, quote_chunk: thisChunk }; + return { + ...rowp, + sizeSetsSS: pkey, + quotation: _quotation, + quote_chunk, + } }); - const allquotation = chunk.reduce((a, c) => a.concat(c.quotation), []); + // console.log('quotation', chunkSS); + const allquotation = chunkSS.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', chunk, dataSource, sizeSets, sizeSetsByMax); - return { chunk, dataSource, sizeSets: sizeSetsByMax, SSRange, PSRange }; + + return { chunk: chunkSS, dataSource: chunkSS, SSRange, PSRange, ...compactSizeSets }; }; const DOC_TITLE = '地接合同'; @@ -158,7 +220,7 @@ export default class AgencyContract { 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); - console.log('*********************************************************************************************'); + // console.log('*********************************************************************************************'); const document = new Document({ creator: 'China Highlights', @@ -331,23 +393,22 @@ export default class AgencyContract { : [this.createSubHeading(`综费`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_6Q(use_year, agencyProducts['6'])]), ...(isEmpty(agencyProducts['B']) ? [] - : [this.createSubHeading(`超公里`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_B(use_year, 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_JD(use_year, agencyProducts['J'])]), + : [this.createSubHeading(`车费`, { numbering: { reference: 'products-type', level: 0 } }), ...this.createTable_JD(use_year, agencyProducts['J'])]), ...(isEmpty(agencyProducts['Q']) ? [] : [this.createSubHeading(`导游`, { numbering: { reference: 'products-type', level: 0 } }), this.createTable_6Q(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)]), + : [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_JD(use_year, agencyProducts['D'])]), + : [this.createSubHeading(`包价线路`, { numbering: { reference: 'products-type', level: 0 } }), ...this.createTable_JD(use_year, agencyProducts['D'])]), this.createSubHeading(`公司银行账户`, { numbering: { reference: 'products-type', level: 0 } }), this.createInfo_Bank(), this.createSubHeading(`联系资料`, { numbering: { reference: 'products-type', level: 0 } }), @@ -389,7 +450,8 @@ export default class AgencyContract { * 超公里 */ createTable_B(use_year, dataList) { - const { chunk: dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_size']); + // console.log('*********************************************************************************************'); + const { chunk, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_size']); const infoCol = (rowp) => new TableCell({ borders: tableBorderOne, @@ -401,32 +463,41 @@ export default class AgencyContract { }), ], }); + const tableS = sizeSets.reduce((acc, size) => { + const subTable = 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: size.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: '' })] }), + ...size.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: `${ss[0]}` })] })), + ], + }), + ...chunk + .filter((p) => p.sizeSetsSS === size.map((mr) => mr[0]).join(',')) + .map((row, ri) => this.createDetailRowSize(size, row, ri, { infoCol })) + .reduce((a, c) => a.concat(c), []), - 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), - ], - }); + this.createTable_Memo(2 + size.length), + ], + }); + acc.push(subTable); + acc.push(new Paragraph({ text: `` })); + return acc; + }, []); + return tableS; } /** @@ -434,35 +505,42 @@ export default class AgencyContract { * 包价线路 */ createTable_JD(use_year, dataList) { - const { chunk, dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season', 'quote_size']); - // console.log(chunk, dataSource, sizeSets); - - 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), []), + const { dataSource, sizeSets, SSRange, PSRange } = chunkBy(use_year, dataList, ['quote_season', 'quote_size']); + + const tableHeader = this.createTableHeader; + const tableS = sizeSets.reduce((rt, setItem) => { + const subTable = new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [ + tableHeader('项目', 20), + tableHeader(['平季', ...SSRange], 40, { columnSpan: setItem.length }), + tableHeader(['旺季', ...PSRange], 40, { columnSpan: setItem.length }), + ], + }), + new TableRow({ + children: [ + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + ...setItem.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: `${ss[0]}` })] })), + ...setItem.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: `${ss[0]}` })] })), + ], + }), + ...dataSource.filter((p) => p.sizeSetsSS === setItem.map((mr) => mr[0]).join(',')) + .map((row, ri) => this.createDetailRowSize(setItem, row, ri, { withSeason: true })).reduce((a, c) => a.concat(c), []), - this.createTable_Memo(1 + sizeSets.length * 2), - ], - }); + this.createTable_Memo(1 + setItem.length * 2), + ], + }); + rt.push(subTable); + rt.push(new Paragraph({ text: `` })); + return rt; + }, []); + return tableS; } /** * 导游 @@ -559,69 +637,46 @@ export default class AgencyContract { ], }); - 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), []), + const tableS = sizeSets.reduce((acc, size) => { + const subTable = new Table({ + borders: tableBorderNone, + width: { + size: 100, + type: WidthType.PERCENTAGE, + }, + rows: [ + new TableRow({ + children: [ + this.createTableHeader('社会餐厅午餐/晚餐', 25), + this.createTableHeader(['价格', ...SSRange, ...PSRange], 50, { columnSpan: size.length }), + this.createTableHeader('司陪餐补', 25), + ], + }), + new TableRow({ + children: [ + new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: '' })] }), + ...size.map((ss) => new TableCell({ borders: tableBorderOne, verticalAlign: AlignmentType.CENTER, children: [new Paragraph({ text: `${ss[0]}` })] })), + // 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.filter((p) => p.sizeSetsSS === size.map((mr) => mr[0]).join(',')) + .map((row, ri) => this.createDetailRowSize(size, row, ri, { withSize: false, extraCol })).reduce((a, c) => a.concat(c), []), - this.createTable_Memo(1 + sizeSets.length + 1), - ], - }); + this.createTable_Memo(1 + size.length + 1), + ], + }); + acc.push(subTable); + acc.push(new Paragraph({ text: `` })); + return acc; + }, []); + return tableS; } /** * 包价线路 * @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), - ], - }); - } + createTable_D(use_year, dataList) {} createDetailRowSize(sizeSets, rowp, ii, { withSeason = false, withSize = true, infoCol = () => {}, extraCol = () => {} } = {}) { const sizeCol = sizeSets.map((ss) => ss.join('-'));