|
@@ -0,0 +1,418 @@
|
|
|
|
|
+'use strict';
|
|
|
|
|
+
|
|
|
|
|
+const crypto = require('crypto');
|
|
|
|
|
+const audit = require('../const/audit');
|
|
|
|
|
+const changeConst = require('../const/change');
|
|
|
|
|
+const GclGather = require('./gcl_gather').gclGather;
|
|
|
|
|
+
|
|
|
|
|
+class ChangeAiImport {
|
|
|
|
|
+ constructor(ctx) {
|
|
|
|
|
+ this.ctx = ctx;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async getContext(scene, id, transaction, includeLedger = true) {
|
|
|
|
|
+ const ctx = this.ctx;
|
|
|
|
|
+ const configs = {
|
|
|
|
|
+ apply: {
|
|
|
|
|
+ service: 'changeApply',
|
|
|
|
|
+ list: 'changeApplyList',
|
|
|
|
|
+ att: 'changeApplyAtt',
|
|
|
|
|
+ key: 'id',
|
|
|
|
|
+ foreign: 'caid',
|
|
|
|
|
+ feature: 'openChangeApply',
|
|
|
|
|
+ },
|
|
|
|
|
+ plan: {
|
|
|
|
|
+ service: 'changePlan',
|
|
|
|
|
+ list: 'changePlanList',
|
|
|
|
|
+ att: 'changePlanAtt',
|
|
|
|
|
+ key: 'id',
|
|
|
|
|
+ foreign: 'cpid',
|
|
|
|
|
+ feature: 'openChangePlan',
|
|
|
|
|
+ },
|
|
|
|
|
+ change: {
|
|
|
|
|
+ service: 'change',
|
|
|
|
|
+ list: 'changeAuditList',
|
|
|
|
|
+ att: 'changeAtt',
|
|
|
|
|
+ key: 'cid',
|
|
|
|
|
+ foreign: 'cid',
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const config = configs[scene];
|
|
|
|
|
+ if (!config || !id) throw '变更识别参数有误';
|
|
|
|
|
+ if (config.feature && !ctx.subProject.page_show[config.feature]) throw '该功能已关闭';
|
|
|
|
|
+
|
|
|
|
|
+ const service = ctx.service[config.service];
|
|
|
|
|
+ const where = { [config.key]: id, tid: ctx.tender.id };
|
|
|
|
|
+ const change = transaction
|
|
|
|
|
+ ? (await service.getAllDataForUpdate(where, transaction))[0]
|
|
|
|
|
+ : await service.getDataByCondition(where);
|
|
|
|
|
+ const status = audit[config.service].status;
|
|
|
|
|
+ // 与详情页一致:只允许原报人在未上报、退回或修订时导入。
|
|
|
|
|
+ if (!change || String(change.uid) !== String(ctx.session.sessionUser.accountId)
|
|
|
|
|
+ || ![status.uncheck, status.checkNo, status.revise].includes(change.status)) {
|
|
|
|
|
+ throw '当前单据不可导入,请刷新页面检查权限或审批状态';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const listService = ctx.service[config.list];
|
|
|
|
|
+ const detailWhere = { [config.foreign]: id, tid: ctx.tender.id };
|
|
|
|
|
+ const lists = transaction
|
|
|
|
|
+ ? await listService.getAllDataForUpdate(detailWhere, transaction, 'id')
|
|
|
|
|
+ : await listService.getAllDataByCondition({ where: detailWhere, orders: [['id', 'asc']] });
|
|
|
|
|
+ const attachments = await ctx.service[config.att].getAllDataByCondition({
|
|
|
|
|
+ where: detailWhere,
|
|
|
|
|
+ columns: ['id'],
|
|
|
|
|
+ orders: [['id', 'asc']],
|
|
|
|
|
+ }, transaction);
|
|
|
|
|
+ change.decimal = typeof change.decimal === 'string' ? JSON.parse(change.decimal) : change.decimal;
|
|
|
|
|
+ change.decimal = change.decimal || {
|
|
|
|
|
+ tp: ctx.tender.info.decimal.tp,
|
|
|
|
|
+ up: ctx.tender.info.decimal.up,
|
|
|
|
|
+ precision: ctx.tender.info.precision,
|
|
|
|
|
+ };
|
|
|
|
|
+ ctx.change = change;
|
|
|
|
|
+ const ledgerTargets = scene === 'change' && includeLedger ? await this._getLedgerTargets(transaction) : [];
|
|
|
|
|
+ const ledgerVersion = crypto.createHash('sha256').update(JSON.stringify(ledgerTargets)).digest('hex');
|
|
|
|
|
+ const version = crypto.createHash('sha256').update(JSON.stringify({ change, lists, attachments })).digest('hex');
|
|
|
|
|
+
|
|
|
|
|
+ const fields = this._getFields(scene);
|
|
|
|
|
+ const original = {};
|
|
|
|
|
+ for (const field of fields) {
|
|
|
|
|
+ original[field.key] = change[field.key] === null || change[field.key] === undefined ? '' : String(change[field.key]);
|
|
|
|
|
+ }
|
|
|
|
|
+ const classes = (ctx.subProject.fun_set.change_class || []).filter(item => item.checked).map(item => ({
|
|
|
|
|
+ value: String(scene === 'change' ? item.value : item.new_name || item.name),
|
|
|
|
|
+ name: item.new_name || item.name,
|
|
|
|
|
+ aliases: [item.name, item.new_name].filter(Boolean),
|
|
|
|
|
+ }));
|
|
|
|
|
+ const qualities = Object.values(changeConst.quality).map(item => ({
|
|
|
|
|
+ value: String(scene === 'change' ? item.value : item.name),
|
|
|
|
|
+ name: item.name,
|
|
|
|
|
+ aliases: [item.name.replace('设计', '')],
|
|
|
|
|
+ }));
|
|
|
|
|
+ return {
|
|
|
|
|
+ scene,
|
|
|
|
|
+ id,
|
|
|
|
|
+ config,
|
|
|
|
|
+ change,
|
|
|
|
|
+ fields,
|
|
|
|
|
+ original,
|
|
|
|
|
+ lists,
|
|
|
|
|
+ ledgerTargets,
|
|
|
|
|
+ ledgerVersion,
|
|
|
|
|
+ version,
|
|
|
|
|
+ precision: change.decimal.precision || ctx.tender.info.precision,
|
|
|
|
|
+ upDecimal: change.up_decimal === null || change.up_decimal === undefined ? change.decimal.up : change.up_decimal,
|
|
|
|
|
+ options: { class: classes, quality: qualities },
|
|
|
|
|
+ // AI 导入匹配不到已有清单和台账时,允许新增空白清单。
|
|
|
|
|
+ allowAdd: true,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _getFields(scene) {
|
|
|
|
|
+ const relatedKeys = { apply: 'project_code', plan: 'apply_code', change: 'plan_code' };
|
|
|
|
|
+ const fields = [
|
|
|
|
|
+ ['code', '编号'],
|
|
|
|
|
+ ['name', '变更名称'],
|
|
|
|
|
+ ['peg', '桩号'],
|
|
|
|
|
+ [relatedKeys[scene], '关联单据编号'],
|
|
|
|
|
+ ['org_name', '原设计名称'],
|
|
|
|
|
+ [scene === 'change' ? 'org_code' : 'new_code', '原设计图号'],
|
|
|
|
|
+ [scene === 'change' ? 'new_name' : 'design_name', '变更设计名称'],
|
|
|
|
|
+ [scene === 'change' ? 'new_code' : 'c_new_code', '变更设计图号'],
|
|
|
|
|
+ ['class', '变更类别'],
|
|
|
|
|
+ ['quality', '变更性质'],
|
|
|
|
|
+ ];
|
|
|
|
|
+ const textFields = {
|
|
|
|
|
+ apply: [['reason', '变更原因'], ['org_content', '原设计情况'], ['site_content', '现场情况'], ['content', '变更内容']],
|
|
|
|
|
+ plan: [['reason', '拟变更方案'], ['content', '变更内容'], ['memo', '备注'], ['expr', '计算依据']],
|
|
|
|
|
+ change: [['content', '变更内容'], ['basis', '变更依据'], ['expr', '计算依据'], ['memo', '备注']],
|
|
|
|
|
+ };
|
|
|
|
|
+ const result = fields.map(item => ({
|
|
|
|
|
+ key: item[0],
|
|
|
|
|
+ name: item[1],
|
|
|
|
|
+ type: ['class', 'quality'].includes(item[0]) ? 'select' : 'text',
|
|
|
|
|
+ }));
|
|
|
|
|
+ return result.concat(textFields[scene].map(item => ({ key: item[0], name: item[1], type: 'textarea' })));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async save(scene, id, payload, file) {
|
|
|
|
|
+ if (!payload || !['all', 'main', 'items'].includes(payload.extract_scope)
|
|
|
|
|
+ || !Array.isArray(payload.rows) || payload.rows.length > 500) {
|
|
|
|
|
+ throw '导入数据格式有误';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (file && scene === 'apply'
|
|
|
|
|
+ && !changeConst.file_type.some(item => String(item.key) === String(payload.attachment_type))) {
|
|
|
|
|
+ throw '请选择附件资料类型';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const ctx = this.ctx;
|
|
|
|
|
+ const transaction = await ctx.service.change.beginTransaction();
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 加锁后重新检查权限和预览版本,所有写入使用同一个事务。
|
|
|
|
|
+ const needsLedger = payload.extract_scope !== 'main' && payload.rows.some(row => row.action === '台账插入');
|
|
|
|
|
+ const data = await this.getContext(scene, id, transaction, needsLedger);
|
|
|
|
|
+ if (payload.version !== data.version) throw '单据已发生变化或本次导入已保存,请关闭窗口并重新识别';
|
|
|
|
|
+ if (needsLedger && payload.ledgerVersion !== data.ledgerVersion) throw '台账已发生变化,请返回第一步重新识别并核对台账目标';
|
|
|
|
|
+ const info = {};
|
|
|
|
|
+ if (payload.extract_scope !== 'items') {
|
|
|
|
|
+ for (const field of data.fields) {
|
|
|
|
|
+ if (!Object.prototype.hasOwnProperty.call(payload.main || {}, field.key)) continue;
|
|
|
|
|
+ const value = payload.main[field.key];
|
|
|
|
|
+ if (typeof value !== 'string' || value.length > (field.type === 'textarea' ? 60000 : 2000)) {
|
|
|
|
|
+ throw '基本信息字段格式有误';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (value === data.original[field.key]) continue;
|
|
|
|
|
+ if (field.type === 'select' && !data.options[field.key].some(item => item.value === value)) {
|
|
|
|
|
+ throw field.name + '不在系统选项内';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (['code', 'name'].includes(field.key) && !value.trim()) throw field.name + '不能为空';
|
|
|
|
|
+ info[field.key] = field.type === 'select' && scene === 'change' ? Number(value) : value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (info.code && await ctx.service[data.config.service].isRepeat(id, info.code, ctx.tender.id)) {
|
|
|
|
|
+ throw '该编号已存在';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const updates = [];
|
|
|
|
|
+ const inserts = [];
|
|
|
|
|
+ const usedTargets = new Set();
|
|
|
|
|
+ const usedLedgerTargets = new Set();
|
|
|
|
|
+ const beforeKey = scene === 'change' ? 'oamount2' : 'oamount';
|
|
|
|
|
+ const selected = payload.extract_scope === 'main' ? [] : payload.rows;
|
|
|
|
|
+ for (const row of selected) {
|
|
|
|
|
+ if (row.action === '跳过') continue;
|
|
|
|
|
+ if (!['新增', '更新', '台账插入'].includes(row.action)) throw '清单处理方式有误';
|
|
|
|
|
+ const target = row.action === '更新' ? data.lists.find(item => String(item.id) === String(row.target)) : null;
|
|
|
|
|
+ const ledgerTarget = row.action === '台账插入' ? data.ledgerTargets.find(item => item.id === row.target) : null;
|
|
|
|
|
+ if (row.action === '台账插入') {
|
|
|
|
|
+ if (!ledgerTarget) throw '台账清单或部位已失效,请重新选择';
|
|
|
|
|
+ if (usedLedgerTargets.has(ledgerTarget.id) || data.lists.some(item =>
|
|
|
|
|
+ String(item.gcl_id) === String(ledgerTarget.gcl_id) && String(item.mx_id || '') === String(ledgerTarget.mx_id))) {
|
|
|
|
|
+ throw '该台账清单部位已添加或被多行选中,请改为更新已有清单或重新选择';
|
|
|
|
|
+ }
|
|
|
|
|
+ usedLedgerTargets.add(ledgerTarget.id);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (row.action === '更新' && (!target || usedTargets.has(String(target.id)))) {
|
|
|
|
|
+ throw '更新目标不存在或多行选择了同一清单,请重新核对';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (target) usedTargets.add(String(target.id));
|
|
|
|
|
+ if (!target && !ledgerTarget && !data.allowAdd) throw '空白清单添加功能未开启';
|
|
|
|
|
+
|
|
|
|
|
+ const item = {};
|
|
|
|
|
+ if (ledgerTarget) {
|
|
|
|
|
+ Object.assign(item, ledgerTarget);
|
|
|
|
|
+ delete item.id;
|
|
|
|
|
+ delete item.source;
|
|
|
|
|
+ } else if (!target) {
|
|
|
|
|
+ for (const key of ['code', 'name', 'unit']) {
|
|
|
|
|
+ if (typeof row[key] !== 'string' || row[key].length > 500) throw '清单文本格式有误';
|
|
|
|
|
+ item[key] = row[key].trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!item.name || !item.unit) throw '新增清单名称、单位不能为空';
|
|
|
|
|
+ item.unit_price = this._parseNumber(row.price, true);
|
|
|
|
|
+ }
|
|
|
|
|
+ const before = this._parseNumber(row.before, false);
|
|
|
|
|
+ const delta = this._parseNumber(row.delta, false);
|
|
|
|
|
+ const unit = target ? target.unit : item.unit;
|
|
|
|
|
+ const precision = data.change.decimal.precision || ctx.tender.info.precision;
|
|
|
|
|
+ const unitSetting = Object.values(precision || {}).find(value => value.unit === unit);
|
|
|
|
|
+ const digits = unitSetting ? unitSetting.value : precision && precision.other ? precision.other.value : 3;
|
|
|
|
|
+ const syncOriginal = scene === 'change' && !this._hasLedgerReference(ledgerTarget || target);
|
|
|
|
|
+ item[beforeKey] = ctx.helper.round(before, digits);
|
|
|
|
|
+ if (syncOriginal) item.oamount = item[beforeKey];
|
|
|
|
|
+ item.camount = ctx.helper.round(delta, digits);
|
|
|
|
|
+ const sameBefore = target && target[beforeKey] !== null && target[beforeKey] !== undefined
|
|
|
|
|
+ && target[beforeKey] !== '' && Number(target[beforeKey]) === item[beforeKey]
|
|
|
|
|
+ && (!syncOriginal || target.oamount !== null && target.oamount !== undefined
|
|
|
|
|
+ && target.oamount !== '' && Number(target.oamount) === item.oamount);
|
|
|
|
|
+ const sameDelta = target && target.camount !== null && target.camount !== undefined
|
|
|
|
|
+ && target.camount !== '' && Number(target.camount) === item.camount;
|
|
|
|
|
+ if (sameBefore && sameDelta) continue;
|
|
|
|
|
+ if (!sameDelta) {
|
|
|
|
|
+ item.spamount = item.camount;
|
|
|
|
|
+ if (scene === 'change') item.camount_expr = '';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (target) {
|
|
|
|
|
+ if (scene === 'change') await this._checkTarget(target, item);
|
|
|
|
|
+ item.id = target.id;
|
|
|
|
|
+ updates.push(item);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ item.tid = ctx.tender.id;
|
|
|
|
|
+ item[data.config.foreign] = id;
|
|
|
|
|
+ if (item.unit_price !== null) item.unit_price = ctx.helper.round(item.unit_price, data.upDecimal);
|
|
|
|
|
+ if (scene === 'change') {
|
|
|
|
|
+ Object.assign(item, {
|
|
|
|
|
+ lid: '0',
|
|
|
|
|
+ bwmx: '',
|
|
|
|
|
+ samount: '',
|
|
|
|
|
+ detail: '',
|
|
|
|
|
+ gcl_id: '',
|
|
|
|
|
+ mx_id: '',
|
|
|
|
|
+ is_valuation: 1,
|
|
|
|
|
+ delimit: data.change.delimit === undefined || data.change.delimit === null ? 100 : data.change.delimit,
|
|
|
|
|
+ order: data.change.order_by ? Math.max(0, ...data.lists.map(list => Number(list.order) || 0)) + inserts.length + 1 : null,
|
|
|
|
|
+ });
|
|
|
|
|
+ if (ledgerTarget) {
|
|
|
|
|
+ for (const key of ['lid', 'gcl_id', 'mx_id', 'bwmx', 'xmj_code', 'xmj_jldy', 'xmj_dwgc', 'xmj_fbgc', 'xmj_fxgc', 'oamount']) {
|
|
|
|
|
+ item[key] = ledgerTarget[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ await this._checkTarget({ gcl_id: item.gcl_id, mx_id: item.mx_id }, item);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ inserts.push(item);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!Object.keys(info).length && !inserts.length && !updates.length && !file) throw '没有需要保存的变更';
|
|
|
|
|
+
|
|
|
|
|
+ let attachment;
|
|
|
|
|
+ if (file) {
|
|
|
|
|
+ attachment = {
|
|
|
|
|
+ tid: ctx.tender.id,
|
|
|
|
|
+ [data.config.foreign]: id,
|
|
|
|
|
+ uid: ctx.session.sessionUser.accountId,
|
|
|
|
|
+ filename: scene === 'change' ? file.name : file.filename,
|
|
|
|
|
+ fileext: file.ext,
|
|
|
|
|
+ filepath: file.filepath,
|
|
|
|
|
+ filesize: scene === 'change' ? file.size : ctx.helper.bytesToSize(file.size),
|
|
|
|
|
+ extra_upload: false,
|
|
|
|
|
+ };
|
|
|
|
|
+ if (scene === 'change') {
|
|
|
|
|
+ attachment.in_time = Math.floor(Date.now() / 1000);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ attachment.upload_time = new Date();
|
|
|
|
|
+ attachment.type = scene === 'apply' ? Number(payload.attachment_type) : 5;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (Object.keys(info).length) {
|
|
|
|
|
+ await ctx.service[data.config.service].defaultUpdate(info, {
|
|
|
|
|
+ where: { [data.config.key]: id, tid: ctx.tender.id },
|
|
|
|
|
+ }, transaction);
|
|
|
|
|
+ }
|
|
|
|
|
+ const listService = ctx.service[data.config.list];
|
|
|
|
|
+ if (inserts.length) {
|
|
|
|
|
+ await listService.insertDatasSplit(transaction, listService.tableName, inserts);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (updates.length) {
|
|
|
|
|
+ await listService.defaultUpdateRows(updates, transaction);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (inserts.length || updates.length) {
|
|
|
|
|
+ await listService.calcCamountSum(transaction);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (attachment) {
|
|
|
|
|
+ const attachmentService = ctx.service[data.config.att];
|
|
|
|
|
+ await attachmentService.insertDatasSplit(transaction, attachmentService.tableName, [attachment]);
|
|
|
|
|
+ }
|
|
|
|
|
+ await transaction.commit();
|
|
|
|
|
+ return { added: inserts.length, updated: updates.length, fields: Object.keys(info).length };
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ await transaction.rollback();
|
|
|
|
|
+ throw err;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _parseNumber(value, optional) {
|
|
|
|
|
+ if (value === '' || value === null || value === undefined) {
|
|
|
|
|
+ if (optional) return null;
|
|
|
|
|
+ throw '原设计数量和增减数量不能为空,请核对或跳过该行';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(String(value))
|
|
|
|
|
+ || !Number.isFinite(Number(value)) || Math.abs(Number(value)) > 1e15) {
|
|
|
|
|
+ throw '清单数值格式有误';
|
|
|
|
|
+ }
|
|
|
|
|
+ return Number(value);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async _checkTarget(target, values) {
|
|
|
|
|
+ const ctx = this.ctx;
|
|
|
|
|
+ const ready = await ctx.service.settle.getReadySettle(ctx.tender.id);
|
|
|
|
|
+ if (ready && target.gcl_id) {
|
|
|
|
|
+ const service = target.mx_id ? ctx.service.settlePos : ctx.service.settleBills;
|
|
|
|
|
+ const where = { settle_id: ready.id, lid: target.gcl_id };
|
|
|
|
|
+ if (target.mx_id) where.pid = target.mx_id;
|
|
|
|
|
+ const settled = await service.getDataByCondition(where);
|
|
|
|
|
+ if (settled && settled.settle_status === ctx.service.settle.settleStatus.finish) throw '已结算清单不能修改';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!target.id) return;
|
|
|
|
|
+ const used = await ctx.service.stageChange.getAllFinalUsedData(ctx.tender.id, ctx.change.cid);
|
|
|
|
|
+ let quantity = 0;
|
|
|
|
|
+ for (const item of used.filter(item => String(item.cbid) === String(target.id))) {
|
|
|
|
|
+ quantity = ctx.helper.add(quantity, item.qty || 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ if ((quantity > 0 && values.camount < quantity) || (quantity < 0 && values.camount > quantity)) {
|
|
|
|
|
+ throw '清单增减数量不能小于已调用数量范围';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async _getLedgerTargets(transaction) {
|
|
|
|
|
+ const ctx = this.ctx;
|
|
|
|
|
+ const lastRevise = await ctx.service.ledgerRevise.getLastestRevise(ctx.tender.id);
|
|
|
|
|
+ const includeChange = !lastRevise || lastRevise.status === audit.revise.status.checked;
|
|
|
|
|
+ const ledgerCondition = { where: { tender_id: ctx.tender.id }, orders: [['ledger_id', 'asc']] };
|
|
|
|
|
+ const posCondition = { where: { tid: ctx.tender.id }, orders: [['porder', 'asc'], ['id', 'asc']] };
|
|
|
|
|
+ const bills = await ctx.service.ledger.getAllDataByCondition(ledgerCondition, transaction);
|
|
|
|
|
+ const positions = await ctx.service.pos.getAllDataByCondition(posCondition, transaction);
|
|
|
|
|
+ if (includeChange) {
|
|
|
|
|
+ bills.push(...await ctx.service.changeLedger.getAllDataByCondition(ledgerCondition, transaction));
|
|
|
|
|
+ positions.push(...await ctx.service.changePos.getAllDataByCondition(posCondition, transaction));
|
|
|
|
|
+ }
|
|
|
|
|
+ return this.buildLedgerContext(bills, positions).ledgerTargets;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ buildLedgerContext(bills, positions) {
|
|
|
|
|
+ const ctx = this.ctx;
|
|
|
|
|
+ const gather = new GclGather(ctx);
|
|
|
|
|
+ gather.billsTree.loadDatas(bills.slice().sort((a, b) => (Number(a.order) || 0) - (Number(b.order) || 0) || String(a.id).localeCompare(String(b.id))));
|
|
|
|
|
+ const posByLedger = new Map();
|
|
|
|
|
+ for (const pos of positions.slice().sort((a, b) => (Number(a.porder) || 0) - (Number(b.porder) || 0) || String(a.id).localeCompare(String(b.id)))) {
|
|
|
|
|
+ const key = String(pos.lid);
|
|
|
|
|
+ if (!posByLedger.has(key)) posByLedger.set(key, []);
|
|
|
|
|
+ posByLedger.get(key).push(pos);
|
|
|
|
|
+ }
|
|
|
|
|
+ const groups = new Map();
|
|
|
|
|
+ const targets = [];
|
|
|
|
|
+ for (const node of gather.billsTree.nodes) {
|
|
|
|
|
+ if (!node.b_code || node.children.length) continue;
|
|
|
|
|
+ const groupKey = JSON.stringify([node.b_code, node.name || '', node.unit || '', Number(node.unit_price) || 0]);
|
|
|
|
|
+ if (!groups.has(groupKey)) groups.set(groupKey, node.id);
|
|
|
|
|
+ let section = node;
|
|
|
|
|
+ while (section.b_code && gather.billsTree.getParent(section)) section = gather.billsTree.getParent(section);
|
|
|
|
|
+ const peg = gather.getPegNode(section);
|
|
|
|
|
+ const details = posByLedger.get(String(node.id)) || [node];
|
|
|
|
|
+ for (const detail of details) {
|
|
|
|
|
+ const mxId = detail === node ? '' : String(detail.id);
|
|
|
|
|
+ const quantity = Number(detail.quantity) || 0;
|
|
|
|
|
+ targets.push({
|
|
|
|
|
+ id: JSON.stringify([String(node.id), mxId]),
|
|
|
|
|
+ source: 'ledger',
|
|
|
|
|
+ lid: groups.get(groupKey),
|
|
|
|
|
+ gcl_id: node.id,
|
|
|
|
|
+ mx_id: mxId,
|
|
|
|
|
+ code: node.b_code,
|
|
|
|
|
+ name: node.name || '',
|
|
|
|
|
+ unit: node.unit || '',
|
|
|
|
|
+ unit_price: node.unit_price === undefined ? null : node.unit_price,
|
|
|
|
|
+ oamount: quantity,
|
|
|
|
|
+ oamount2: quantity,
|
|
|
|
|
+ camount: 0,
|
|
|
|
|
+ bwmx: detail.name !== node.name ? detail.name || section.name || '' : section.name || '',
|
|
|
|
|
+ xmj_code: section.code || '',
|
|
|
|
|
+ xmj_jldy: section.name || '',
|
|
|
|
|
+ xmj_dwgc: gather.getDwgc(peg, section),
|
|
|
|
|
+ xmj_fbgc: gather.getFbgc(peg, section),
|
|
|
|
|
+ xmj_fxgc: gather.getFxgc(peg, section),
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ const ledgerVersion = crypto.createHash('sha256').update(JSON.stringify(targets)).digest('hex');
|
|
|
|
|
+ return { ledgerTargets: targets, ledgerVersion };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _hasLedgerReference(item) {
|
|
|
|
|
+ return !!item && [item.lid, item.gcl_id, item.mx_id].some(value => {
|
|
|
|
|
+ const id = value === null || value === undefined ? '' : String(value).trim();
|
|
|
|
|
+ return id !== '' && id !== '0';
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+module.exports = ChangeAiImport;
|