spring+springmvc+jpa的集成:
1.jar包的依赖
spring-web spring对web的支持
Spring-mvc SpringMVC的支持
Spring-orm spring对关系数据的支持
Spring-DataSource Spring对连接池的支持
hibernate对jpa的支持
mysql的包–对数据库的支持
Jackson的包–Spring对Jackson的支持
Spring-jdbc的包
Spring织物的包–切面
2.集成思路
jdbc.properties—>datasource—>EntityManagerfactory—>dao---->service---->controller
1.jdbc.properties 配置数据库的相关配置
文件:jdbc.properties 位置 resources
2.applicationContext.xml
文件:applicationContext 位置 resources
1.连接池的配置
配置datasource
2.扫描配置文件
3.注解的支持---service repository
4.jpa的配置 EntityManagerfactory
5.事物的开启扫描和配置
6.datajpa的配置
3.applicationContext-mvc.xml
文件:applicationContext-mvc 位置 resources
1.mvc注解的配置
2.静态资源配置
3.包的扫描
4.视图解析器的配置
5.文件上传下载 名字唯一 multipartResolver
4.web.xml
文件:web.xml 位置WEB/INF
1.dispatcherServlet的配置
包含加载时间 文件加载applicationContext-mvc.xml
2.文件监听器的配置
3.文件的加载---applicationContext.xml
4.编码问题
3.domain层的抽取
抽取商品的id为父类共有的字段
4.分页对象的抽取Query
父接口BaseQuery–》
字段:
crrentPage 默认是1 提供专供我们查询的0开始索引方法
pageSize 默认10
orderByType 默认true 升序
orderByName 默认无
方法:
createSort是否排序
抽象方法 createSpecification 要求子类按照自己的方式高级查询
子实现EmployeeQuery–>
字段
employee的字段
方法
覆写抽象方法createSpecification 按照username email age的高级查询
3.repository的抽取(dao层)—>datajpa功能扩展
分析见图SpringDataJpa的功能扩展.png
4.完成service层
思路:
父接口IBaseService—>CRUD 查询
父实现BaseServiceImpl—>开启事物 注入dao层BaseRepository 完成功能
IEmployeeService 继承IBaseService
EmployeeServiceImpl 实现接口IEmployeeService 继承BaseServiceImpl
5.mvc的配置
配置web.xml
controller层的建立(注意开启注解扫描)
6.页面的准备
main.jsp 中含有树状菜单tree(通过menu.json拿到数据)的layout布局,
layout布局
head.jsp的抽取
把引入jquery的支持抽取出来 图标支持 css样式支持 中文支持 js支持 颜色支持 插件支持
引入<%@include file="/WEB-INF/views/head.jsp"%>
树菜单的tree
读取写死的json数据
页签tabls
在树状菜单中注册方法,点击添加一个页签,没有就新增,有就选中
显示数据采用iframe显示:
<iframe scrolling="auto" frameborder="0" src="'+url+'" style="width:100%;height:100%;"></iframe>
employee.jsp
显示employee数据
问题:---》easyui的数据显示与datajpa查询的数据不符合
解决:接受用employeeQuery对象新增两个方法接送前台传来的值
新增一个对象,封装datajpa的数据返回的total rows
7.部门的展示
部门类及相关的准备
employee中配置外键
问题:
1.显示的图片为地址 使用formatter
value:当前列对应字段值。
row:当前的行记录数据。
index:当前的行下标。
直接方法方法返回值
2. no session
3.未序列化问题
8.高级查询
使用form包裹 方便使用jquery.jdirk.js的插件功能 直接将form中的属性封装成json对象
var params = searchForm.serializeObject();
employeeGrid.datagrid(‘load’,params);
问题:使用不了
解决方法 filename传参
9.删除功能
1.选中删除 未选中对话框 选中确认发生ajax请求
2.后台提供一个删除jsonresult对象 返回是否成功(private Boolean sucessful=true;//默认返回true 删除成功) 错误信息( private String mig;)
10.新增功能
1.将form表单放入dialog中
1.设置对话框dialog的属性为模态框 modal:true
每次点击弹出对话框 employeedialog.dialog(‘open’);
每次弹出的位置固定 employeedialog.dialog(‘center’);
2.数据验证功能
1.引入插件
2.年龄整数范围验证
data-options=“validType:‘integerRange[18,60]’”
3.密码二次确定验证
二次密码的一个验证框
注意密码的验证validType=“equals[‘password’,‘id’] //上一个验证框的id=password进行查找验证
4.自定义验证用户名
js中直接加
.
e
x
t
e
n
d
(
.extend(
.extend(.fn.validatebox.defaults.rules, {
checkName: {
validator: function(value,param){
var result = $.ajax({
url: “/employee/check”,
data:{username:value},
async: false//false发送同步请求
}).responseText;
return result===“true”;
},
message: ‘该用户名已经被占用,请重新输入.’
}
})
注意事项:(value,param) value为当前验证框输入值 param为传的参数数组
这里需要使用发送ajax同步请求 返回值为字符串
3.form重新开启应当自动清除上一次输入的内容
$(”#employeeform").form(‘clear’);
4.修改功能
1.应该隐藏密码框和验证密码框
在密码框 和验证密码框加属性 data-show
增加中
$(“*[data-show]”).show() //显示密码框
$(“*[data-show] input”).validatebox("enable");//启用验证
修改中:
$(“*[data-show]”).hid()//隐藏密码框
$(“*[data-show] input”).validatebox("disable");//禁用验证
2.修改应该验证用户名
用户名为当前用户名可用,其他的为正常验证
if(id!=null){
if(employeeServiceImpl.findOne(id).getUsername().equals(username)){
return true;
}
}
return employeeServiceImpl.checkName(username);
5.保存功能
使用from的保存方法
$('#ff').form('submit', {
url: ...,
onSubmit: function(){
var isValid = $(this).form('validate');//拿到验证内容
if (!isValid){
$.messager.progress('close'); // 如果表单是无效的则隐藏进度条
}
return isValid; // 返回false终止表单提交
},
success: function(){
$.messager.progress('close'); // 如果提交成功则隐藏进度条
}
});
区别新增与修改:
使用隐藏域传入id,通过id判断是新增(URL="/employee/save")修改(URL= "/employee/edict?cmd=cmd")
后台通过不同的方法进行处理
问题:
1.数据丢失(修改保存数据,部门字段没有传入值,数据丢失)
解决方法:
1.使用隐藏域将值传入 保密性不好麻烦
2.在保存的时候自己使用jpql不改未变动的字段
3.使用Spring的方法ModelAttribute
//这里准备一个方法,所有方法执行前都会执行它
@ModelAttribute("editEmployee")
public Employee beforeEdit(Long id,String cmd){
//解决数据丢失问题,准备两个字段 修改传入参数才执行
if(id!=null&&cmd!=null){
Employee employee = employeeServiceImpl.findOne(id);
//把这个要修改的关联对象设置为null,可以解决n-to-n的问题
employee.setDepartment(null);
return employee;
}
return null;
}
在接受的参数@ModelAttribute("editEmployee")Employee employee,Spring会自动将修改的值保存到查出来的持久化对象里
2.n to n的问题
11.代码生成器
模板技术velocity用来代码生成器,静态化页面、制作邮件,短信发送
可以是.vm结尾的模板
使用步骤:
1.导入相关的jar
org.apache.velocity
velocity
1.6
2.
//创建模板应用上下文
VelocityContext context = new VelocityContext();
context.put(“msg”, “小张是个好同志”);
//拿到相应的模板(需要设置好编码)
Template template = Velocity.getTemplate(“temptest/hello.html”,“UTF-8”);
//准备输出流(输出文件时使用文件字符流)
StringWriter writer = new StringWriter();
template.merge(context, writer);
System.out.println(writer);
代码生成器:
配置:导入plugins—>EasyCode-1.2.1-RELEASE.zip
步骤:1.创建自定义模板
2.使用velocity语法
3.注意在外面生成的路径为公共的如:包名cn.xxx.easysell 文件名E:/soft/software/idea/workspace/EasySell2/src
注意: 严格区别大小写
对表中不能直接生成代码(如抽取了父类的代码),要先筛选
当不确定列数,既有可能生成多列的要使用遍历
12.权限
密码—》加盐----》多次加密 基本不可破解
密码的验证:拿到密码进行相同的加密方式得到的结果进行判断
密码的修改:只能直接修改密码
权限:
权限与用户是多对多的关系 其中通过角色来串联
权限与资源有一对一 多对一(不用)一对多的关系
用户进入系统:角色可以是游客 游客在使用对应功能的时候需要令牌进行,系统应该根据用户的角色授权
shiro与Spring的集成:
.
applicationContext-shiro:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Shiro's main business-tier object for web-enabled applications
(use DefaultSecurityManager instead when there is no web environment)-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="jdbcRealm"/>
</bean>
<bean id="jdbcRealm" class="cn.xxx.easysell.shiro.jdbcRealm">
<property name="name" value="jdbcRealm"/>
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密算法-->
<property name="hashAlgorithmName" value="MD5"/>
<!--加密次数-->
<property name="hashIterations" value="10"/>
</bean>
</property>
</bean>
<!--用做注解配置权限方法-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after
the lifecycleBeanProcessor has run: -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- Secure Spring remoting: Ensure any Spring Remoting method invocations can be associated
with a Subject for security checks. -->
<bean id="secureRemoteInvocationExecutor" class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--loginUrl:没有登录,就会进入的页面
successUrl:登录成功,进入这个页面
unauthorizedUrl:没有权限,进入这个页面
shiroFilter这个名字需要与web.xml里面的名字一致
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/shiro/success.jsp"/>
<property name="unauthorizedUrl" value="/shiro/unauthorized.jsp"/>
<!-- 设置权限和资源
annon不需要权限和登录也可以访问的页面
authc需要登录才能访问
-->
<!--<property name="filterChainDefinitions">-->
<!--<value>-->
<!--/shiro/login.jsp= anon-->
<!--/login=anon-->
<!--/** = authc-->
<!--</value>-->
<!--</property>-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
<property name="filters" >
<map>
<entry key="ajaxFilter" value-ref="ajaxPermissionsAuthorizationFilter"/>
</map>
</property>
</bean>
<!--用来设置自定义权限拦截跳转-->
<bean id="ajaxPermissionsAuthorizationFilter" class="cn.xxx.easysell.common.AjaxPermissionsAuthorizationFilter"></bean>
<!--用来设置权限的问题-->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapFactory" factory-method="createfilterChainDefinitionMap"/>
<bean id="filterChainDefinitionMapFactory" class="cn.xxx.easysell.shiro.filterChainDefinitionMapFactory"/>
</beans>
13.登录
login重复请求不同请求方式问题:
在requestmapping注解中加入请求方式
@RequestMapping(value = “login”,method = RequestMethod.GET)
@RequestMapping(value = “login”,method = RequestMethod.POST)
请求发生ajax请求:
在发生登录验证请求的时候
解决二次带卡不是顶层页面的问题:
// 检查自己是否是顶级页面
if (top != window) {// 如果不是顶级
//把子页面的地址,赋值给顶级页面显示
window.top.location.href = window.location.href;
}
解决注销问题:
shiro提供了标签直接可以使用标签引出 注意引入shirotaglib
<%@ taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>
shiro:user
欢迎[<shiro:principal />]登录,退出
</shiro:user>
解决回车登录问题:
$(document.documentElement).on(“keyup”, function(event) {
//console.debug(event.keyCode);
var keyCode = event.keyCode;
console.debug(keyCode);
if (keyCode === 13) { // 捕获回车
submitForm(); // 提交表单
}
});
登录过期,页面重叠问题:
// 检查自己是否是顶级页面
if (top != window) {// 如果不是顶级
//把子页面的地址,赋值给顶级页面显示
window.top.location.href = window.location.href;
}
14.多对多 角色和权限
配置:
用户与角色----》多对多的关系
在用户配置角色的外键(list)
角色与权限----》多对多的关旭
在角色配置角色的外键(list)
前台页面显示问题:
需求:在前台角色分页展示的时候,把角色对应的权限也显示出来
分析:已经配置好了权限的外键,角色的权限的id已经拿出对应权限的值(即通过中间表拿到权限的名字),只需要将名字拿出
问题:
1.取出角色对应的权限(是数组):在前台拿到的对应权限的为一个数组
解决:通过在权限提供一个方法将对应的权限数组遍历出来,然后将名字取出
<%–将角色对应的权限取出来–%>
权限
//遍历所有权限取出名字
function formatpermisson(value,row,index) {
var permisson="";
for(let per of value){
permisson+=" "+per.name;
}
return permisson;//formatter方法能够将返回的任意值显示
}
需求:需要添加或者修改角色对应的权限
分析:在弹出的添加框增加一个数据表格---》修改时:显示已知角色对应的权限(新增就为空)增加一个数据表格所有的权限(即权限数据表格)
问题:
1.弹出的时候才添加两个数据表格
解决:将两个数据表格动态生成
perGrid.datagrid({
url:'/permission/page',
fit:true,
fixed:true,
fitColumns:true,
pagination:true,
/*双击增加*/
onDblClickRow:addPermission
});
//加载左侧角色数据表格
beforegrid.datagrid({
fit:true,
fixed:true,
fitColumns:true,
/**双击减少*/
onDblClickRow:removePermission
});
2.双击增加权限表格新增 已经存在弹出提示 双击角色表格取消权限
新增解决:
权限表格--->双击增加触发事件---->获取列--->判断角色表格的列的id与获取的列是否相同 相同弹出提示 不相同新增
取消解决:
角色表格--->双击增加触发事件---->取消列(重新载入表格)
3.关闭表格,依然显示有选择的权限
新增打开应该讲角色数据表格数据置空
//当前用户权限清空
beforegrid.datagrid("loadData",[]);
4.修改表格,保存表格的时候取消权限,未保存,权限表格下次会减少找个权限
原因:两个表格共用一个权限数据,应当操作两份数据
var permissions = [];
$.extend(permissions,row.permissions);//将权限表格的数据拷贝给角色表格操作
beforegrid.datagrid("loadData",permissions);
5.无法保存权限
原因:后台的List 我们需要特别传参给后台
/*权限是一个数组 所以我们需要单独传参/
onSubmit: function(parm){//parm是我们传递到后台的参数
var rows=KaTeX parse error: Expected 'EOF', got '#' at position 3: ("#̲beforegrid").da…{i}].id`]=rows[i].id;
}
//拿到验证信息
var isValid = $(this).form(‘validate’);
if (!isValid){
$.messager.progress(‘close’); // 如果表单是无效的则隐藏进度条
}
return isValid; // 返回false终止表单提交
},
15.多对一 菜单的权限查询 自连接菜单的查询
分析:菜单与权限为多对一的关系 权限为多方(外键在权限)菜单为一方
1.在权限Permission配置外键Meun对象(多个权限对应一个菜单)
2.因为有需求:即通过菜单来查询多个菜单下的权限
在Permission配置menu的list,但是如果交给jpa管理的话,它会把这个菜单对应的权限全部查询出来(可能我只有这个菜单下的部门功能)
所以要脱离jpa管理@Transient
3.菜单存在子查询 即通过自己的父菜单查询到下面的子菜单
在menu配置子查询外键Menu parent 声明外键名字和抓取策略
4.需求:返回json形式的子父菜单json信息
分析:提供一个容器菜单 通过用户的id查询到子类菜单 在通过遍历子类菜单拿到父类菜单(通过外键查询到)判断容器菜单是否有此父菜单
有就只添加子菜单
@Override
public List
//通过id查出所有子菜单
List<Menu> childrenMenu = menuRepository.getChildrenMenu(id);
//通过遍历子外键查询到对应的父菜单
for (int i=0;i<childrenMenu.size();i++){
//拿到子菜单
Menu children = childrenMenu.get(i);
//通过子菜单外键查询到父菜单
Menu parent = children.getParent();
if(meus.contains(parent)){
parent.getChildren().add(children);
}else {
meus.add(parent);
parent.getChildren().add(children);
}
}
return meus;
}
注意:返回的字段必须是json格式 包括子节点名字children必须是children字段
提供返回getText方法返回name
16.shiro对无效ajax请求定义显示undefined(在没有权限进行操作的时候)
需求:shiro对ajax无效请求显示undefined(在没有权限进行操作的时候)
分析:需要对ajax的无效请求单独处理,因为shiro默认处理没有权限的请求就是跳转页面,不符合ajax请求
设置没有权限,对请求进行判断,如果是ajax请求就返回json数据
实现:
/**
-
PermissionsAuthorizationFilter:
-
解决shiro对权限的拦截,发送ajax请求出现undefined问题
/
public class AjaxPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
/*
*
*- @param request
- @param response
- @return
- @throws IOException
*思路: - 覆写onAccessDenied方法,通过判断请求头来确定是否是ajax请求
- 在通过返回resp输出流输出输出json字符串(转移字符)
- 配置
*/
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
this.saveRequestAndRedirectToLogin(request, response);
} else {
//没有权限进入
HttpServletRequest req= (HttpServletRequest) request;
HttpServletResponse resp= (HttpServletResponse) response;
String xRequestedWith = req.getHeader(“X-Requested-With”);
//判断是否ajax请求LHttpRequest
if(“XMLHttpRequest”.equals(xRequestedWith)){
resp.setContentType(“javaScript/json; charset=UTF-8”);
resp.getWriter().print("{“success”:false,“msg”:“没有权限做这个操作”}");
System.out.println(11111);
return false;
}
String unauthorizedUrl = this.getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(401);
}
}return false;
}
}
需要交给shiro管理:shiro.xml
<!--用来设置自定义权限拦截跳转-->
注意在工厂设置权限的时候需要与配置key名字一致:
//通过读取数据库读取信息
List permissions = permissionService.findAll();
permissions.forEach(e->{
map.put(e.getUrl(), “ajaxFilter[”+e.getSn()+"]");
});
map.put("/**", “authc”);
17.办公文件的支持(POI对Excel的支持)
1.导入POI的包
2.下载
需求:根据用户的需要下载员工到数据库
思路: 1.在相关需要导出的类的字段打上注解
2.通过前台传来的query对象进行查询(复习:前台的高级查询,是通过将form中的表单信息,通过数据表格load方法,重新加载,然后将form中的对象封装在query复习,发送分页请求page来返回数据)
将query对象查询到员工集合,将员工集合返给poi对象 (注意图片需要通过拿到动态路径进行拼接)
3.POI的相关操作:
//设置一些属性
ExportParams params = new ExportParams(“员工管理”, “明细”, ExcelType.XSSF);
//params.setFreezeCol(3);
map.put(NormalExcelConstants.DATA_LIST, list); // 数据集合
map.put(NormalExcelConstants.CLASS, Employee.class);//导出实体
map.put(NormalExcelConstants.PARAMS, params);//参数
map.put(NormalExcelConstants.FILE_NAME, “employee”);//文件名称
//返回的名称 :easypoiExcelView -> 并没有找我的bean,而且当做一个路径去进行访问
// 现在默认去找的视图解析器,而没有找我的那一个bean
return NormalExcelConstants.EASYPOI_EXCEL_VIEW;//View名称
4.出现视图解析器的设置自动跳转页面,我们需要将返回的字符串找到指定的POI类,让他响应文件
配置bean的视图解析器,让他先找返回字符串是注解的在自动拼接
<context:component-scan base-package=“cn.afterturn.easypoi.view” />
3.上传
需求:用户能够上传表格到数据库,我们需要对里面的数据进行判断
思路:我们需要拿到里面的内容进行验证---->使用第三方jar和自定义验证–>将有效数据存入 返回无效数据提示
步骤:
1.JSR 303 规范验证包
15.订单模块
springmvc对日期:
前台时间显示get的时候加上@JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”,timezone = “GMT+8”)
后台时间拿到在set方法@DateTimeFormat(pattern = “yyyy-MM-dd”)
订单和订单明细:为组合关系,强聚合,需要配置双向的一对多和多对一,最强级联和孤儿删除,而且需要双方都能找到对方,即
//从一方能找到多方 多方找到一方
//拿到一方的多方
List purchasebillitemList = purchasebill.getPurchasebillitems();
//遍历多方存入一方
for(Purchasebillitem item:purchasebillitemList){
//遍历拿到的所有明细 设置到保存到一方
item.setPurchasebill(purchasebill);
item.setAmount(item.getNum().multiply(item.getPrice()));
totalamount=totalamount.add(item.getAmount());
totalnum=totalnum.add(item.getNum());
}
SyntaxError: JSON.parse: unexpected end of data at line 1 column 668980 of the JSON data
问题来源:在进行订单的返回时,由于配置了双向的一对多和多对一,订单找明细 明细找订单的重复次数
解决:配置jsonigore忽略明细找订单
16.报表模块
提供数据表格(datagrid-groupview)和饼状图(HighChart)的报表模式:
数据表格的实现:通过datagrid-groupview实现
思路:1.数据需要单独一个对象来接受(避免过多无用数据的查询)----VO的使用(单独用来特殊功能的对象),装有我们需要的数据字段
2.前台通过对query对象赋值来 进行类型的数据展示(如按供应商、销售员、月份)报表,提供高级查询支持,将查询条件拼接,注意查询内容的位置
3.返回一个list装有我们的vo对象来进行展示
3d饼图的实现:通过highChart实现
思路:1.引入相关的js(注意js的路径在windows不区分大小写,引入失败找不到,需要查看target包下的路径是否正确)
2.发生ajax请求,将我们需要的高级查询条件传入,返回的值为饼状的data值(注意是一个键值形式)
3.我们的query对象需要的: 1.一个分组的jpql语句:通过判断前台传来的值来进行判断分组是通过 供应商 销售员 月份(月份使用MONTH(vdate)取出)
2.一个条件判断的语句:通过判断前台传来的值来进行StringBulider进行拼接,同时提供一个list对条件进行存储(因为条件需要通过?来进行赋值)
4.通过拼接的jpql(其中总计为:sum(小计)),传入lquery中的条件list(需要将其转换为数组,可变参数为数组,而不是需要一个一盒对象)
拿到装有一个object[]数组的list 遍历list将其值存入map中 key=“name” value=obj[0] key=“y” value=obj[1]
将map装入List返回前台
17.业务模块
基础模块 业务模块 其他模块 库存模块 销售模块 财务模块 物流模块
基础模块:员工管理 部门管理 数据字典
权限模块:基本一样 权限模块是可以直接用
进货模块、库存模块、销售模块(核心业务)
采购单的流程:
1.根据需求部门提出的需求提出采购申请表
即相关人员进入系统添加一致采购申请表(组合关系)
2.采购员找到供应商,询问价格,入库时间,保存一张咨询采购表
3.相关部门根据采购金额的大小进行评审,对供应商咨询采购产品价格列表。
4.进行采购环节,下采购单采购部经理审核,财务部有一个应付款
5.客户发货,发货单
6.准备采购入库单
财务模块:
应付款 应收款 (有零钱的结算忽略问题)
库存模块:
采购入库单、销售入库单、退货单、换货单、调货单、报损报溢单
盘点单