flexbiz-server 12.4.8 → 12.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "flexbiz-server",
3
3
  "main": "./server/app.js",
4
4
  "description": "Flexible Server",
5
- "version": "12.4.8",
5
+ "version": "12.4.10",
6
6
  "author": {
7
7
  "name": "Van Truong Pham",
8
8
  "email": "invncur@gmail.com"
@@ -102,7 +102,8 @@ const rptHandler = async (ctrl,req,callback,res=undefined)=> {
102
102
  console.log(`✅ [rptHanlder] sessionID=${getCurrentSession()?._debugId}`);
103
103
  getData(req, async (error, data, stream,from_cache) => {
104
104
  if (error) {
105
- if (!sendHeader_yn || !options.stream || !res) return callback({error:error.message||error});
105
+ console.error(`❌ [rptHanlder] ${rptId} lỗi:`,error);
106
+ if (!sendHeader_yn || !options.stream || !res) return callback(error);
106
107
  if (_interval) clearInterval(_interval);
107
108
  setTimeout(() => {
108
109
  res.end(JSON.stringify({error: error.toString()}));
@@ -1,10 +1,183 @@
1
- const async=require("async"),_=require("lodash"),moment=require("moment"),permission=require("../libs/permission"),utils=require("../libs/utils"),{JSONParser}=require("../libs/utils"),{handlerWithSession,onAfterCommit}=require("../libs/sessionContext.js"),updateBookHanlder=($ctrl$$,$req$$,$mainCallback$$)=>{let $model$$=$ctrl$$.model;async.series([function($callback$$){setImmediate(async()=>{if(!$ctrl$$.post)return $callback$$({error:"Ch\u1ee9c n\u0103ng n\u00e0y kh\u00f4ng c\u00f3 t\u00ednh n\u0103ng post s\u1ed5 s\u00e1ch"});
2
- let $condition$$={};for(var $k_ngay_ks_tu_ngay$$ in $req$$.query)if($k_ngay_ks_tu_ngay$$!="id_app"&&$k_ngay_ks_tu_ngay$$!="access_token")if($k_ngay_ks_tu_ngay$$=="_id"&&global.mongoose.Types.ObjectId.isValid($req$$.query._id))try{let $id$$=global.mongoose.Types.ObjectId($req$$.query._id);$condition$$._id=$id$$}catch($error$$){console.error("postAgain condition",$error$$)}else{if($k_ngay_ks_tu_ngay$$=="q"&&$req$$.query[$k_ngay_ks_tu_ngay$$])try{$condition$$=JSONParser($req$$.query[$k_ngay_ks_tu_ngay$$]);
3
- continue}catch($e$$){return console.error("postAgain parse condition",$e$$),$callback$$($e$$)}if($k_ngay_ks_tu_ngay$$=="tu_ngay"&&_.has($model$$.schema.paths,"ngay_ct")===!0){var $val_val$$=moment($req$$.query.tu_ngay).startOf("date").toDate();$condition$$.ngay_ct?$condition$$.ngay_ct.$gte=$val_val$$:$condition$$.ngay_ct={$gte:$val_val$$}}else $k_ngay_ks_tu_ngay$$=="den_ngay"&&_.has($model$$.schema.paths,"ngay_ct")===!0?($val_val$$=moment($req$$.query.den_ngay).endOf("date").toDate(),$condition$$.ngay_ct?
4
- $condition$$.ngay_ct.$lte=$val_val$$:$condition$$.ngay_ct={$lte:$val_val$$}):_.has($model$$.schema.paths,$k_ngay_ks_tu_ngay$$)===!0&&($condition$$[$k_ngay_ks_tu_ngay$$]=$req$$.query[$k_ngay_ks_tu_ngay$$])}_.has($model$$.schema.paths,"id_app")===!0&&($condition$$.id_app=$req$$.user.current_id_app);if(_.has($model$$.schema.paths,"ngay_ct")){if(!$condition$$.ngay_ct)return $callback$$({error:"B\u1ea1n ch\u01b0a nh\u1eadp \u0111i\u1ec1u ki\u1ec7n cho ng\u00e0y ch\u1ee9ng t\u1eeb"});$k_ngay_ks_tu_ngay$$=
5
- $condition$$.ngay_ct.$lte||$condition$$.ngay_ct.$lt;if(!$k_ngay_ks_tu_ngay$$)return $callback$$({error:"\u0110i\u1ec1u ki\u1ec7n ng\u00e0y ch\u1ee9ng t\u1eeb kh\u00f4ng h\u1ee3p l\u1ec7"});$k_ngay_ks_tu_ngay$$=new Date($k_ngay_ks_tu_ngay$$);if($k_ngay_ks_tu_ngay$$=await utils.isBookLocked({id_app:$req$$.user.current_id_app,ngay_ct:$k_ngay_ks_tu_ngay$$}))return $callback$$({error:`\u0110\u00e3 kh\u00f3a s\u1ed5 \u0111\u1ebfn h\u1ebft ng\u00e0y ${moment($k_ngay_ks_tu_ngay$$).format("DD/MM/YYYY")}`})}$ctrl$$.finding?
6
- $ctrl$$.finding($req$$.user,$condition$$,function($e$$,$condition$$){setImmediate(()=>{if($e$$)return console.error("postAgain finding",$e$$),$callback$$({error:$e$$.message||$e$$.error||$e$$});$ctrl$$.dynamicFinding?$ctrl$$.dynamicFinding($req$$.user,$condition$$,function($e$$,$condition$$){if($e$$)return console.error("postAgain dynamicFinding",$e$$),$callback$$({error:$e$$.message||$e$$.error||$e$$});$req$$.condition=$condition$$;$callback$$()},{req:$req$$}):($req$$.condition=$condition$$,$callback$$())})},
7
- {req:$req$$}):$ctrl$$.dynamicFinding?$ctrl$$.dynamicFinding($req$$.user,$condition$$,function($e$$,$condition$$){if($e$$)return console.error("postAgain dynamicFinding",$e$$),$callback$$({error:$e$$.message||$e$$.error||$e$$});$req$$.condition=$condition$$;$callback$$()},{req:$req$$}):($req$$.condition=$condition$$,$callback$$())})},function($callback$$){permission.isAdmin($req$$.user.current_id_app,$req$$.user.email,function($e$$,$admin$$){setImmediate(()=>{if($e$$)return $callback$$({error:$e$$.error||
8
- $e$$,code:$e$$.code});if(!$admin$$)return $callback$$({error:"B\u1ea1n ph\u1ea3i c\u00f3 quy\u1ec1n admin \u0111\u1ec3 th\u1ef1c hi\u1ec7n t\u00ednh n\u0103ng n\u00e0y"});$callback$$()})})},function($callback$$){const $condition$$=$req$$.condition;_.has($model$$.schema.paths,"ma_ct")&&$ctrl$$.options.isVoucher&&($condition$$.ma_ct=$ctrl$$.name.toUpperCase());onAfterCommit(()=>{let $data_log$$={...$req$$.query};delete $data_log$$.access_token;$req$$.user&&$ctrl$$.name!=="log"&&$ctrl$$.name!=="labelinfo"&&
9
- $ctrl$$.name!=="listinfo"&&$ctrl$$.name!=="reportinfo"&&$ctrl$$.name!=="moduleinfo"&&$ctrl$$.name!=="options"&&global.getModel("log").create({id_app:$req$$.user.current_id_app,id_func:$ctrl$$.name,action:"POSTAGAIN",data:{condition:JSON.stringify($data_log$$)}},$req$$.user.email,$req$$.user_agent,$req$$)});$ctrl$$.postAgainHandler($req$$.user,$condition$$,($e$$,$rs$$)=>{if($e$$)return $callback$$({error:$e$$.message||$e$$.error||$e$$});$req$$.result=$rs$$;$callback$$()},{req:$req$$})}],$e$$=>{if($e$$)return $mainCallback$$($e$$);
10
- $mainCallback$$(null,$req$$.result)})},withSesssion=async($ctrl$$,$req$$,$callback$$)=>{handlerWithSession(updateBookHanlder,$ctrl$$,$req$$,$callback$$)};module.exports=withSesssion;
1
+ const async = require("async");
2
+ const _ = require("lodash");
3
+ const moment = require("moment");
4
+ const permission = require('../libs/permission');
5
+ const utils = require('../libs/utils');
6
+ const {JSONParser} = require('../libs/utils');
7
+ const { handlerWithSession, onAfterCommit } = require("../libs/sessionContext.js");
8
+ const updateBookHanlder = (ctrl,req,mainCallback)=> {
9
+ let model = ctrl.model;
10
+ async.series([
11
+ function(callback) {
12
+ setImmediate(async ()=>{
13
+ if (!ctrl.post) {
14
+ return callback({error:"Chức năng này không có tính năng post sổ sách"});
15
+ }
16
+ let condition = {};
17
+ for (let k in req.query) {
18
+ if (k == 'id_app' || k == 'access_token') {
19
+ continue;
20
+ }
21
+ if (k == '_id' && global.mongoose.Types.ObjectId.isValid(req.query._id)) {
22
+ try {
23
+ let id = global.mongoose.Types.ObjectId(req.query._id);
24
+ condition._id = id;
25
+ } catch (error) {
26
+ console.error("❌ [updateBookhandler] postAgain condition",error);
27
+ }
28
+ continue;
29
+ }
30
+ if (k == 'q' && req.query[k]) {
31
+ try{
32
+ let q = JSONParser(req.query[k]);
33
+ condition = q;
34
+ continue;
35
+ }catch(e){
36
+ console.error("❌ [updateBookhandler] postAgain parse condition",e);
37
+ return callback(e);
38
+ }
39
+ }
40
+ if (k == 'tu_ngay' && _.has(model.schema.paths, 'ngay_ct') === true) {
41
+ //let val = new Date(req.query.tu_ngay);
42
+ let val = moment(req.query.tu_ngay).startOf("date").toDate();
43
+ //
44
+ if (condition.ngay_ct) {
45
+ condition.ngay_ct.$gte = val;
46
+ } else {
47
+ condition.ngay_ct = {
48
+ $gte: val
49
+ };
50
+ }
51
+ continue;
52
+ }
53
+ if (k == 'den_ngay' && _.has(model.schema.paths, 'ngay_ct') === true) {
54
+
55
+ //let val = new Date(req.query.den_ngay);
56
+ let val = moment(req.query.den_ngay).endOf("date").toDate();
57
+
58
+ if (condition.ngay_ct) {
59
+ condition.ngay_ct.$lte = val;
60
+ } else {
61
+ condition.ngay_ct = {
62
+ $lte: val
63
+ };
64
+ }
65
+ continue;
66
+ }
67
+ if (_.has(model.schema.paths, k) === true) {
68
+ condition[k] = req.query[k]
69
+ }
70
+ }
71
+
72
+ if (_.has(model.schema.paths, 'id_app') === true) {
73
+ condition.id_app = req.user.current_id_app;
74
+ }
75
+ //check ngay chung tu
76
+ if(_.has(model.schema.paths, 'ngay_ct')){
77
+ if(!condition.ngay_ct){
78
+ console.error("❌ [updateBookhandler] Thiếu điều kiện ngày chứng từ",condition);
79
+ return callback({error:`Điều kiện cập nhật lại sổ sách thiếu ngày chứng từ`});
80
+ }
81
+
82
+ let tu_ngay = condition.ngay_ct.$lte || condition.ngay_ct.$lt;
83
+ if(!tu_ngay) return callback({error:`Điều kiện ngày chứng từ không hợp lệ`});
84
+ tu_ngay = new Date(tu_ngay);
85
+ let ngay_ks = await utils.isBookLocked({id_app:req.user.current_id_app,ngay_ct:tu_ngay});
86
+ if(ngay_ks){
87
+ console.error("❌ [updateBookhandler] Đã khóa sổ đến hết ngày",ngay_ks);
88
+ return callback({error:`Đã khóa sổ đến hết ngày ${moment(ngay_ks).format("DD/MM/YYYY")}`});
89
+ }
90
+ }
91
+ //
92
+ if (ctrl.finding) {
93
+ ctrl.finding(req.user, condition, function(e, condition) {
94
+ setImmediate(()=>{
95
+ if (e){
96
+ console.error("❌ [updateBookhandler] postAgain finding",e)
97
+ return callback({error:e.message || e.error || e});
98
+ }
99
+ if (ctrl.dynamicFinding) {
100
+ ctrl.dynamicFinding(req.user, condition, function(e, condition) {
101
+ if (e){
102
+ console.error("❌ [updateBookhandler] postAgain dynamicFinding",e)
103
+ return callback({error:e.message || e.error || e});
104
+ }
105
+ req.condition = condition;
106
+ callback();
107
+ },{req})
108
+ }else{
109
+ req.condition = condition;
110
+ callback();
111
+ }
112
+ })
113
+ }, {
114
+ req: req
115
+ });
116
+ } else {
117
+ if (ctrl.dynamicFinding) {
118
+ ctrl.dynamicFinding(req.user, condition, function(e, condition) {
119
+ if (e){
120
+ console.error("❌ [updateBookhandler] postAgain dynamicFinding",e)
121
+ return callback({error:e.message || e.error || e});
122
+ }
123
+ req.condition = condition;
124
+ callback();
125
+ },{req})
126
+ }else{
127
+ req.condition = condition;
128
+ callback();
129
+ }
130
+ }
131
+ })
132
+ },
133
+ //check permission
134
+ function(callback) {
135
+ permission.isAdmin(req.user.current_id_app, req.user.email, function(e, admin) {
136
+ setImmediate(()=>{
137
+ if (e) return callback({error:e.error||e,code:e.code});
138
+ if (!admin) {
139
+ return callback({error:"Bạn phải có quyền admin để thực hiện tính năng này"});
140
+ }
141
+ callback();
142
+ })
143
+ });
144
+ },
145
+ function(callback) {
146
+ const condition = req.condition;
147
+ //require ma_ct
148
+ if (_.has(model.schema.paths, 'ma_ct') && ctrl.options.isVoucher){
149
+ condition.ma_ct = ctrl.name.toUpperCase();
150
+ }
151
+ //
152
+ onAfterCommit(()=>{
153
+ //write log
154
+ let data_log = {...req.query};
155
+ delete data_log.access_token;
156
+ req.user && ctrl.name!=="log" && ctrl.name!=="labelinfo" && ctrl.name!=="listinfo" && ctrl.name!=="reportinfo" && ctrl.name!=="moduleinfo" && ctrl.name!=="options" && global.getModel("log").create({
157
+ id_app: req.user.current_id_app,
158
+ id_func: ctrl.name,
159
+ action: 'POSTAGAIN',
160
+ data: {
161
+ condition: JSON.stringify(data_log)
162
+ }
163
+ }, req.user.email, req.user_agent, req);
164
+ })
165
+ ctrl.postAgainHandler(req.user,condition,(e,rs)=>{
166
+ if(e) return callback({error:e.message || e.error || e});
167
+ req.result=rs;
168
+ callback();
169
+ },{req})
170
+ }
171
+ ],(e)=>{
172
+ if(e){
173
+ return mainCallback(e);
174
+ }
175
+ mainCallback(null,req.result);
176
+ })
177
+ }
178
+
179
+
180
+ const withSesssion = async (ctrl,req,callback)=>{
181
+ handlerWithSession(updateBookHanlder,ctrl,req,callback)
182
+ }
183
+ module.exports =withSesssion;
@@ -88,7 +88,7 @@ exports.handlerWithSession = async function (handler, ctrl, req, callback, ...ex
88
88
 
89
89
  try {
90
90
  if (error) {
91
- console.error("❌ [handlerWithSession] error in handler, aborting...", ctrl.name, error.message);
91
+ console.error("❌ [handlerWithSession] error in handler, aborting...", ctrl.name, error.message,error?.errorLabels);
92
92
  await session.abortTransaction();
93
93
  reject(error);
94
94
  } else {
@@ -173,19 +173,21 @@ exports.handlerWithSession = async function (handler, ctrl, req, callback, ...ex
173
173
  * Thêm callback để chạy sau khi commit.
174
174
  * {string} description miêu tả cb để hiện log (tuỳ chọn)
175
175
  */
176
- exports.onAfterCommit = function (cb,description="") {
177
- const store = storage.getStore();
178
- if (store && store.session && exports.isSessionActive(store.session)) {
176
+ exports.onAfterCommit = function (cb,description="",session=null,store=null) {
177
+
178
+ store = store || storage.getStore();
179
+ session = session || store?.session
180
+ if (store && session && exports.isSessionActive(session)) {
179
181
  // ✅ Lưu vào store.afterCommit
180
182
  store.afterCommit = store.afterCommit || [];
181
183
  store.afterCommit.push(cb);
182
184
  console.log(`[onAfterCommit] added callback, storeId=${store.storeId}, count=${store.afterCommit.length},des=${description}`);
183
185
  } else {
184
186
  // Không có session → chạy ngay
185
- console.log("[onAfterCommit] no active session, running immediately",description);
187
+ console.log("[onAfterCommit] no active session, running immediately",description,", storeId=",store?.storeId);
186
188
  Promise.resolve()
187
189
  .then(cb)
188
- .catch((err) => console.error("[onAfterCommit immediate error]", err,description));
190
+ .catch((err) => console.error("[onAfterCommit immediate error]", err,description,", storeId=",store?.storeId));
189
191
  }
190
192
  };
191
193
 
@@ -1,4 +1,48 @@
1
- const redisCache=require("../libs/redis-cache"),{onAfterCommit}=require("../libs/sessionContext"),assuser_identitySchema=new Schema({id_app:{type:String,required:!0,maxlength:1024},ten_cmnd:{type:String},so_cmnd:{type:String,required:!0,maxlength:32},trang_thai:{type:String},status:{type:Boolean,default:!0},date_created:{type:Date,default:Date.now},date_updated:{type:Date,default:Date.now},user_created:{type:String,default:""},user_updated:{type:String,default:""}});
2
- (global.configs||{}).createIndexes&&(assuser_identitySchema.index({id_app:1,trang_thai:1,so_cmnd:1}),assuser_identitySchema.index({id_app:1,so_cmnd:1},{unique:!0}),assuser_identitySchema.index({so_cmnd:"text"}),assuser_identitySchema.index({status:1}),assuser_identitySchema.index({date_created:1}),assuser_identitySchema.index({date_updated:1}),assuser_identitySchema.index({user_created:1,visible_to:1,visible_to_users:1}));
3
- assuser_identitySchema.post("save",async function($doc$$,$next$$){onAfterCommit(()=>{$doc$$.trang_thai==="1"&&global.getModel("user").findOne({email:$doc$$.user_created},async($error$$,$u$$)=>{console.log("of user",$u$$);$u$$&&($u$$.name=$doc$$.ten_cmnd,$u$$.local=$u$$.local||{},$u$$.local.name=$doc$$.ten_cmnd,await $u$$.save(),await global.getModel("participant").updateOne({email:$doc$$.user_created,id_app:$doc$$.id_app},{name:$doc$$.ten_cmnd}),redisCache.set("user",$u$$.toObject(),function($e$$){$e$$?
4
- console.error($e$$):console.log("cache user infomation to redis")}));$next$$()});$next$$()})});module.exports=mongoose.models.assuser_identity||mongoose.model("assuser_identity",assuser_identitySchema);
1
+ const redisCache = require("../libs/redis-cache");
2
+ const assuser_identitySchema = new Schema({
3
+ id_app: {type: String, required: true, maxlength: 1024},
4
+ ten_cmnd:{type: String},
5
+ so_cmnd: {type: String, required: true, maxlength: 32},
6
+ trang_thai: {type: String},
7
+ status: {type: Boolean, default: true},
8
+ date_created: {type: Date, default: Date.now},
9
+ date_updated: {type: Date, default: Date.now},
10
+ user_created: {type: String, default: ''},
11
+ user_updated: {type: String, default: ''}
12
+ });
13
+ if((global.configs||{}).createIndexes){
14
+ assuser_identitySchema.index({id_app: 1, trang_thai: 1, so_cmnd: 1});
15
+ assuser_identitySchema.index({id_app: 1,so_cmnd: 1},{unique: true });
16
+ assuser_identitySchema.index({so_cmnd: "text"});
17
+ assuser_identitySchema.index({status: 1});
18
+ assuser_identitySchema.index({date_created: 1});
19
+ assuser_identitySchema.index({date_updated: 1});
20
+ assuser_identitySchema.index({user_created: 1,visible_to: 1,visible_to_users: 1});
21
+ }
22
+ assuser_identitySchema.post("save",async function(doc,next){
23
+ if(doc.trang_thai==="1"){
24
+ let User = global.getModel("user");
25
+ User.findOne({email:doc.user_created},async (error,u)=>{
26
+ console.log("of user",u);
27
+ if(u){
28
+ u.name = doc.ten_cmnd;
29
+ u.local = u.local ||{};
30
+ u.local.name = doc.ten_cmnd;
31
+ await u.save();
32
+ await global.getModel("participant").updateOne({email:doc.user_created,id_app:doc.id_app},{name:doc.ten_cmnd});
33
+ //cache
34
+ redisCache.set("user", u.toObject(), function(e) {
35
+ if(e)
36
+ console.error(e)
37
+ else
38
+ console.log("cache user infomation to redis")
39
+ }
40
+ );
41
+ }
42
+ next()
43
+ })
44
+ }
45
+ next();
46
+
47
+ });
48
+ module.exports = mongoose.models.assuser_identity || mongoose.model('assuser_identity', assuser_identitySchema);
@@ -1,6 +1,6 @@
1
1
  const moment = require("moment");
2
2
  const {createSocaiTC} = require("../libs/utils")
3
- const {onAfterCommit} = require("../libs/sessionContext")
3
+ const {onAfterCommit,getCurrentSession,getCurrentStore} = require("../libs/sessionContext")
4
4
  const socaiSchema = new Schema({
5
5
  id_app: {type: String, required: true, maxlength: 1024},
6
6
  ma_dvcs: {type: String, required: true, maxlength: 1024},
@@ -199,87 +199,125 @@ const handleCreateSocaiTc = async ()=>{
199
199
  console.error("handleCreateSocaiTc",doc,e);
200
200
  }
201
201
  socaiIsHandling = false;
202
- setTimeout(()=>{
203
- handleCreateSocaiTc();
204
- },10)
202
+ handleCreateSocaiTc();
205
203
  }
206
204
  }
207
205
  socaiSchema.post("save",async function(doc){
208
- onAfterCommit(()=>{
209
- //tạo socaitheongay
210
- setImmediate(async ()=>{
211
- //cập nhật ngày mua gần nhất của khác hàng
212
- if(["SO1","HD2","HD1","PBL"].indexOf(doc.ma_ct)>=0){
213
- if(doc.ma_kh_no){
214
- let dhgn = await global.getModel("socai").findOne({ma_kh_no:doc.ma_kh_no,id_app:doc.id_app},{ngay_ct:1}).sort({ngay_ct:-1});
215
- if(dhgn){
216
- const so_ngay_da_mua = moment().diff(moment(dhgn.ngay_ct),"days");
217
- global.getModel("customer").updateOne({ma_kh:doc.ma_kh_no,id_app:doc.id_app},{ngay_mua_gan_nhat:dhgn.ngay_ct,so_ngay_da_mua});
218
- }
219
- }
220
- if(doc.ma_kh_co && doc.ma_kh_co!=doc.ma_kh_no){
221
- let dhgn = await global.getModel("socai").findOne({ma_kh_co:doc.ma_kh_co,id_app:doc.id_app},{ngay_ct:1}).sort({ngay_ct:-1});
222
- if(dhgn){
223
- const so_ngay_da_mua = moment().diff(moment(dhgn.ngay_ct),"days");
224
- global.getModel("customer").updateOne({ma_kh:doc.ma_kh_co,id_app:doc.id_app},{ngay_mua_gan_nhat:dhgn.ngay_ct,so_ngay_da_mua});
225
- }
206
+ // Lấy session từ document
207
+ const session = doc.$session() || getCurrentSession();
208
+ const store = getCurrentStore();
209
+ // Định nghĩa hàm xử
210
+ const handleSaveAfterCommit = async () => {
211
+ //cập nhật ngày mua gần nhất của khác hàng
212
+ if(["SO1","HD2","HD1","PBL"].indexOf(doc.ma_ct)>=0){
213
+ if(doc.ma_kh_no){
214
+ let dhgn = await global.getModel("socai").findOne({ma_kh_no:doc.ma_kh_no,id_app:doc.id_app},{ngay_ct:1}).sort({ngay_ct:-1});
215
+ if(dhgn){
216
+ const so_ngay_da_mua = moment().diff(moment(dhgn.ngay_ct),"days");
217
+ global.getModel("customer").updateOne({ma_kh:doc.ma_kh_no,id_app:doc.id_app},{ngay_mua_gan_nhat:dhgn.ngay_ct,so_ngay_da_mua});
226
218
  }
227
219
  }
228
- let key = JSON.stringify({id_app:doc.id_app,ngay_ct:doc.ngay_ct});
229
- if(!socaiQueue.find(d=>d==key)){
230
- socaiQueue.push(key);
231
- if(!socaiIsHandling){
232
- handleCreateSocaiTc();
220
+ if(doc.ma_kh_co && doc.ma_kh_co!=doc.ma_kh_no){
221
+ let dhgn = await global.getModel("socai").findOne({ma_kh_co:doc.ma_kh_co,id_app:doc.id_app},{ngay_ct:1}).sort({ngay_ct:-1});
222
+ if(dhgn){
223
+ const so_ngay_da_mua = moment().diff(moment(dhgn.ngay_ct),"days");
224
+ global.getModel("customer").updateOne({ma_kh:doc.ma_kh_co,id_app:doc.id_app},{ngay_mua_gan_nhat:dhgn.ngay_ct,so_ngay_da_mua});
233
225
  }
234
226
  }
235
- })
236
- },"Xử sự kiện sau khi lưu socai")
227
+ }
228
+ let key = JSON.stringify({id_app:doc.id_app,ngay_ct:doc.ngay_ct});
229
+ if(!socaiQueue.find(d=>d==key)){
230
+ socaiQueue.push(key);
231
+ if(!socaiIsHandling){
232
+ handleCreateSocaiTc();
233
+ }
234
+ }
235
+ };
236
+
237
+ if (session) {
238
+ // Nếu có session, thêm vào danh sách
239
+ onAfterCommit(handleSaveAfterCommit, "Xử lý sự kiện sau khi lưu socai",session,store);
240
+ } else {
241
+ // Không có session (chạy ngoài transaction), chạy ngay
242
+ try {
243
+ await handleSaveAfterCommit();
244
+ } catch (e) {
245
+ console.error("❌ post save immediate error:", e);
246
+ }
247
+ }
237
248
  });
238
249
 
239
250
  socaiSchema.pre('deleteMany', async function (next) {
240
- onAfterCommit(async ()=>{
241
- let query = this.getQuery();
251
+
252
+ // 1. Lấy session từ Query object
253
+ const session = this.options.session || getCurrentSession();
254
+ const store = getCurrentStore();
255
+
256
+ // 2. Định nghĩa hàm xử lý
257
+ const handleDeletionAfterCommit = async () => {
258
+ let query = this.getQuery();
242
259
  let deletedDocs =(await global.getModel("socai").find(query,{id_app:1,ngay_ct:1}).lean()).map(r=>{
243
260
  return JSON.stringify({id_app:r.id_app,ngay_ct:r.ngay_ct});
244
261
  })
245
- setImmediate(()=>{
246
- deletedDocs = [...new Set(deletedDocs)];
247
- deletedDocs.forEach(doc=>{
248
- if(!socaiQueue.find(d=>d==doc)){
249
- socaiQueue.push(doc);
250
- }
251
- })
252
- if(!socaiIsHandling){
253
- handleCreateSocaiTc();
262
+ deletedDocs = [...new Set(deletedDocs)];
263
+ deletedDocs.forEach(doc=>{
264
+ if(!socaiQueue.find(d=>d==doc)){
265
+ socaiQueue.push(doc);
254
266
  }
255
-
256
267
  })
257
- next();
258
- },"Xử lý sự kiện trước khi xoá socai")
259
-
268
+ if(!socaiIsHandling){
269
+ handleCreateSocaiTc();
270
+ }
271
+ };
272
+
273
+ if (session) {
274
+ // 3. Nếu có session, thêm vào danh sách afterCommit
275
+ onAfterCommit(handleDeletionAfterCommit, "Xử lý sự kiện sau khi xoá socai",session,store);
276
+ next(); // KẾT THÚC hook ngay lập tức, cho phép deleteMany chạy
277
+ } else {
278
+ // 4. Nếu không có session, chạy ngay lập tức (dùng next(err) nếu lỗi)
279
+ try {
280
+ await handleDeletionAfterCommit();
281
+ next();
282
+ } catch (e) {
283
+ next(e);
284
+ }
285
+ }
260
286
  });
261
287
  socaiSchema.pre('deleteOne', async function (next) {
262
- onAfterCommit(async ()=>{
263
- let query = this.getQuery();
288
+ // 1. Lấy session từ Query object
289
+ const session = this.options.session || getCurrentSession();
290
+ const store = getCurrentStore();
291
+ // 2. Định nghĩa hàm xử lý
292
+ const handleDeletionAfterCommit = async () => {
293
+ let query = this.getQuery();
264
294
  let deletedDocs =(await global.getModel("socai").find(query,{id_app:1,ngay_ct:1}).lean()).map(r=>{
265
295
  return JSON.stringify({id_app:r.id_app,ngay_ct:r.ngay_ct});
266
296
  })
267
- setImmediate(()=>{
268
- deletedDocs = [...new Set(deletedDocs)];
269
- deletedDocs.forEach(doc=>{
270
- if(!socaiQueue.find(d=>d==doc)){
271
- socaiQueue.push(doc);
272
- }
273
- })
274
- if(!socaiIsHandling){
275
- setTimeout(()=>{
276
- handleCreateSocaiTc();
277
- },1*60*1000)
297
+
298
+ deletedDocs = [...new Set(deletedDocs)];
299
+ deletedDocs.forEach(doc=>{
300
+ if(!socaiQueue.find(d=>d==doc)){
301
+ socaiQueue.push(doc);
278
302
  }
279
-
280
303
  })
281
- next();
282
- },"Xử lý sự kiện trước khi xoá socai")
304
+ if(!socaiIsHandling){
305
+ handleCreateSocaiTc();
306
+ }
307
+ };
308
+ if (session) {
309
+ // 3. Nếu có session, thêm vào danh sách afterCommit
310
+ onAfterCommit(handleDeletionAfterCommit, "Xử lý sự kiện trước khi xoá socai",session,store);
311
+ next(); // KẾT THÚC hook ngay lập tức, cho phép deleteMany chạy
312
+ } else {
313
+ // 4. Nếu không có session, chạy ngay lập tức (dùng next(err) nếu lỗi)
314
+ try {
315
+ await handleDeletionAfterCommit();
316
+ next();
317
+ } catch (e) {
318
+ next(e);
319
+ }
320
+ }
283
321
  });
284
322
  const model = mongoose.models.socai || mongoose.model('socai', socaiSchema);
285
323
  if((global.configs||{}).createIndexes){