[99python]我的分享:用python完成融合二开99%的开发任务,涉及代码。原创
金蝶云社区-小小的梦
小小的梦
18人赞赏了该文章 118次浏览 未经作者许可,禁止转载编辑于2024年12月20日 11:56:20
summary-icon摘要由AI智能服务提供

本文是一位拥有10年软件开发经验的程序员所写,他经历了从后端到前端再到全栈的开发历程,并感受到行业的兴衰。他在2023年7月开始接触金蝶ERP的开发,通过学习和实践,构建了自己的经验性二开底层。文章主要介绍了他用Python进行金蝶ERP二次开发的过程,包括引入必要的库和文件、解释代码、提供数据库查询和操作的示例、以及控制单据体和服务器请求的代码片段。

        我是一个从事软件开发10年程序员,从后端做到前端,再到全栈一站式程序员,见过行业风口,创业开过公司,也经历过低谷,人也到中年,三十五岁,却感受到了这个行业的兴衰,程序开发这东西,建构在中间层东西,总像逆水行舟,稍微不学习就被新的技术开发顶替,也不知道自己还能保持一直学习的状态再坚持几年,总感觉脑子不够用。所以自己写这个文章,也是为了证明这个体系,我来试过。

        2023年7月接触金蝶ERP的开发,从什么都不会,到2023年9月份正式上系统跟着公司找的外包项目上线运用小组,经历了不少。大大小小二开做了不少。做了归纳,也构建了自己的经验性的二开底层。不一定适合所有人,如果你一开始就用,那就一定适合。正如标题”用python完成融合二开99%的开发任务“,说到底其实就是自己懒,不想用C#,编译替换dll配置,这个样的开发模式,对一个一直跑的生产环境,一个人开发是灾难性的。    

        先说我自己的对金蝶python开发的理解,就是BOS开发工具,设置插件配置写入一段python代码保存,按事件函数跑一圈,相关就执行。知道原理,其实事情就好办了,就是去写对应的事件代码。而这些事件,就是程序员二开的主要内容。逛遍社区,其实不难发现,出现最多的就是,函数事件、sql、和数据结果的处理。我本次内容就是针对这部分,做自己的方法的重构。把这个sql和数据结果,变成系统性方法,方便开发单据和简单报表。下面贴代码了,简单做解释:

import clr
import sys
clr.AddReference('mscorlib')
clr.AddReference('System')
clr.AddReference('System.Data')
clr.AddReference("System.Web.Extensions")
clr.AddReference('Kingdee.BOS')
clr.AddReference('Kingdee.BOS.Core')
clr.AddReference('Kingdee.BOS.App')
clr.AddReference('Kingdee.BOS.App.Core')
clr.AddReference('Kingdee.BOS.ServiceHelper')
clr.AddReference("Kingdee.BOS.ServiceFacade.Common")
clr.AddReference('Kingdee.BOS.DataEntity')
clr.AddReference("Newtonsoft.Json")
from Kingdee.BOS import *
from Kingdee.BOS.Util import *
from Kingdee.BOS.Core import *
from Kingdee.BOS.Core.Bill import *
from Kingdee.BOS.Core.List import *
from Kingdee.BOS.Core.Permission import *
from Kingdee.BOS.Core.DynamicForm import *
from Kingdee.BOS.Core.DynamicForm.PlugIn import *
from Kingdee.BOS.Core.DynamicForm.PlugIn.ControlModel import *
from Kingdee.BOS.Core.DependencyRules import *
from Kingdee.BOS.App.Data import *
from Kingdee.BOS.Orm.DataEntity import *
from Kingdee.BOS.ServiceFacade import *
from Kingdee.BOS.KDThread import *
from System import *
from System.Data import *
from System.Threading import *
from System.IO import *
from System.Net import *
from System.Text import *
from System.Security.Cryptography import *
from System.Web.Script.Serialization import *
from System.Collections.Generic import List
from System.Collections.Generic import Dictionary
from Kingdee.BOS.ServiceHelper import *
from Newtonsoft.Json.Linq import *
from Kingdee.BOS.Core.Report import *
def require(p,root_path = r'D:\files\python'):
	read='';
	pyfile=Path.Combine(r'Z:\python',p);
	exs=File.Exists(pyfile);
	if not exs:
		pyfile=Path.Combine(root_path,p);
		exs=File.Exists(pyfile);
		if not exs:
			return '';
	sr = StreamReader(pyfile,Encoding.Default);
	read=sr.ReadToEnd();
	sr.Close();
	return read.replace('\r','');
exec(require('odbc.py'));
exec(require('common.py'));

引入文件,基本上是必须的,按需求可以去掉部分引入,不懂都是干嘛的,可以直接粘贴。

def require(p,root_path = r'D:\files\python'):
	read='';
	pyfile=Path.Combine(r'Z:\python',p);
	exs=File.Exists(pyfile);
	if not exs:
		pyfile=Path.Combine(root_path,p);
		exs=File.Exists(pyfile);
		if not exs:
			return '';
	sr = StreamReader(pyfile,Encoding.Default);
	read=sr.ReadToEnd();
	sr.Close();
	return read.replace('\r','');
exec(require('odbc.py'));
exec(require('common.py'));

解释一下这一块,就是对自己封装的python类进行引入,具体路径就是Z:\python和D:\files\python,前者是加速读写盘,优先读取,负载繁重的系统才需要。正常python类文件放D:\files\python即可。

obdc.py文件内容见附件:

写不下。本文最重要的内容,精华都在这个文件里。

common.py文件内容见附件:

写不下。

以上两个文件的基本使用方法简单例子,如果有疑问可以留言我在做详细解释。

 ls=Db.name('T_PRD_INSTOCKENTRY').noas('t').field('t.FREALQTY,t2.FCONVERT,t2.FNUMBER').join('T_PRD_INSTOCK t1','t.FID = t1.FID').join('T_BD_MATERIAL t2','t.FMATERIALID = t2.FMATERIALID').where(""" AND t2.FNUMBER LIKE '%{2}%' 
AND t1.FCREATEDATE>'{0}' AND t1.FCREATEDATE<'{1}'""".format(start,end,likeAorB)).where({"t1.FPRDORGID":og.Id,"t1.FDOCUMENTSTATUS":"C","t1.FISREWORK":0}).select();
  ls=Db.name('T_PRD_INSTOCKENTRY').noas('t').field('t.FREALQTY,t2.FCONVERT,t2.FNUMBER').join('T_PRD_INSTOCK t1','t.FID = t1.FID').join('T_BD_MATERIAL t2','t.FMATERIALID = t2.FMATERIALID').where(""" AND t2.FNUMBER LIKE '%B%' 
AND t1.FCREATEDATE>'{0}' AND t1.FCREATEDATE<'{1}'""".format(start,end)).where({"t1.FPRDORGID":og.Id,"t1.FDOCUMENTSTATUS":"C","t1.FISREWORK":0}).select();
def BarItemClick(e):
    if e.BarItemKey=="debug":
        Db=ODBC('XY_EWX_USEROPENTRY');
        Db.log(this.Context.UserId);
        Db.rlog();
    if e.BarItemKey=="clearcache":
        Db=ODBC('XY_EWX_USEROPENTRY');
        Db.where("AND FEntryID>0 AND FOPTIME<'{0}'".format(DateTime.Now.AddDays(-int(cer)))).delect();
        Db.name('XY_EWX_USERENTRY').where("AND FEntryID>0 AND FTIME<'{0}'".format(DateTime.Now.AddYears(-int(ler)))).delect();
        this.View.ShowMessage('清除成功');

简单账表的服务器插件例子见附件:

写不下。

再提供几个选择使用的文件:

entry.py控制单据体代码:

def upMoveDown(isMoveUp=True,entryKey='FEntity'):
	entryEntity = this.View.BusinessInfo.GetEntryEntity(entryKey);
	entityDataObject = this.View.Model.GetEntityDataObject(entryEntity);
	rowIndex = this.View.Model.GetEntryCurrentRowIndex(entryKey);
	length=len(entityDataObject);
	if isMoveUp:
		if (rowIndex==0):
			return;
		else:
			movindex=rowIndex-1;
	else:
		if (rowIndex==length-1):
			return;
		else:
			movindex=rowIndex+1;
	this.View.SetEntityFocusRow(entryKey, movindex);
	this.View.Model.CopyEntryRow(entryKey,rowIndex,-1);
	this.View.Model.CopyEntryRow(entryKey,movindex,-1);
	Entity = this.View.BusinessInfo.GetEntryEntity(entryKey);
	Object = this.View.Model.GetEntityDataObject(Entity);
	lgt=len(Object);
	initDir=["Seq","BeginInit","DataEntityState","EndInit","Equals","Events","GetDataEntityType","GetHashCode","GetType","Initialized","IsInitialized","MemberwiseClone","OnInitialized","OnPropertyChanged","OnPropertyChanging","Parent","PropertyChanged","PropertyChanging","ReferenceEquals","ToString","__class__","__delattr__","__doc__","__format__","__getattribute__","__hash__","__init__","__new__","__reduce__","__reduce_ex__","__repr__","__setattr__","__sizeof__","__str__","__subclasshook__"]
	dirs = dir(Object[lgt-1]);
	for key in dirs:
		if key not in initDir:
			this.View.Model.SetValue(key,Object[lgt-1][key],rowIndex);
	dirs = dir(Object[lgt-2]);
	for key in dirs:
		if key not in initDir:
			this.View.Model.SetValue(key,Object[lgt-2][key],movindex);
	this.View.Model.DeleteEntryRow(entryKey,lgt-1);
	this.View.Model.DeleteEntryRow(entryKey,lgt-2);
def createRow(key,lans,bat=True):
	entity = this.View.BusinessInfo.GetEntity(key);
	entryDy = this.View.Model.GetEntityDataObject(entity);
	entryLen=len(entryDy);
	if entryLen<lans and bat:
		this.Model.BatchCreateNewEntryRow(key,lans-entryLen);
	else:
		lens=int(entryLen/10);
		lens=lens if lens>10 else 10;
		for i in range(lens):
			for j in range(lans,entryLen+1):
				this.Model.DeleteEntryRow(key, j);

http.py控制服务器发起请求的类:

class HTTP():
	version='1.0.5';
	headers={};
	options={};
	def __init__(self,headers={}):
		self.headers=headers;
		self.initOptions();
		return self;
	def initOptions(self):
		self.options={'headers':self.headers,'ContentType':'application/json','Timeout':30000};
	def header(self,headers):
		self.options['headers']=headers;
		return self;
	def timeout(self,t):
		self.options['Timeout']=t;
		return self;
	def contype(self,t):
		self.options['ContentType']=t;
		return self;
	def get(self,url):
		webRequest = WebRequest.Create(url)
		webRequest.Method = "GET";
		webRequest.Timeout = self.options['Timeout'];
		if self.options['headers']<>{}:
			for key,val in self.options['headers'].items():
				webRequest.Headers.Set(key, val);
		webResponse = webRequest.GetResponse();
		stream = webResponse.GetResponseStream();
		streamReader =StreamReader(stream, Encoding.GetEncoding("utf-8"))
		result = streamReader.ReadToEnd();
		if not result:
			return False;
		else:
			res=self.decode(result);
			if not res:
				return result;
			else:
				return res;
	def post(self,url,postdata=''):
		if not isinstance (postdata,str):
			postdata=self.encode(postdata);
		webRequest = HttpWebRequest.Create(url);
		webRequest.Method = "POST"
		webRequest.ContentType = self.options['ContentType'];
		webRequest.Timeout = self.options['Timeout'];
		if self.options['headers']<>{}:
			for key,val in self.options['headers'].items():
				webRequest.Headers.Set(key, val);
		data=Encoding.UTF8.GetBytes(postdata);
		webRequest.ContentLength = data.Length;
		webRequest.GetRequestStream().Write(data, 0, data.Length)
		webRequest.GetRequestStream().Flush()
		webRequest.GetRequestStream().Close()
		webResponse  =webRequest.GetResponse()
		streamReader =StreamReader( webResponse .GetResponseStream(),Encoding.GetEncoding("utf-8") )
		result=streamReader.ReadToEnd()
		if not result:
			return False;
		else:
			res=self.decode(result);
			if not res:
				return result;
			else:
				return res;
	def encode(self,obj):#json_encode
		jsonSerializerProxy=JsonSerializerProxy(Encoding.GetEncoding("utf-8"),False)
		return jsonSerializerProxy.Serialize(obj)
	def decode(self,s):#json_decode
		return JObject.Parse(s);
	def time(self,time=DateTime.Now):
		ts = time  - DateTime.Parse('1970-01-01 08:00:00');
     		return Convert.ToInt64(ts.TotalMilliseconds)/1000;

延续的开发模式还可以衍生,金蝶做电脑端,利用webapi,和金蝶的phpapi文件,开发的H5端控,实现手机端直接下推单据,增删改查,不外挂另外的控制后台。这是我开发简单mes系统。上一些相关图,可以看看。image.png

image.png

image.png

image.pngimage.png

            以上是工作需要,开发的内容,只是分享,也希望有人能看到。人生也迷茫的,每个行业都每个行业的规则,有站在转变的档口上了,不知道走向那个方向了。有的人说是Ai,也做过开发,也试过融合,可能是自己的能量太过微小。迷茫归迷茫,对我自己价值来说,总要有个选择,目前选择了手机游戏行业。自己刚刚构建好一套自己的threejs的游戏开发底层框架,内容正在开发,如果能走下去话,我对金蝶,这篇文章是我的留恋。说心里话,金蝶挺好,系统给了足够的生态,足够的自由,也包括外包团队的人,他们都是活力满满的,很积极向上,和他们接触很开心。

Desktop.zip(8.37KB)

图标赞 18
18人点赞
还没有人点赞,快来当第一个点赞的人吧!
图标打赏
0人打赏
还没有人打赏,快来当第一个打赏的人吧!