├── .codeclimate.yml ├── .gitignore ├── LICENSE ├── README.md ├── addons ├── buy │ ├── __init__.py │ ├── __openerp__.py │ ├── buy.py │ ├── buy_data.xml │ └── buy_view.xml ├── core │ ├── __init__.py │ ├── __openerp__.py │ ├── core.py │ └── core_view.xml ├── demo │ └── __init__.py ├── goods │ ├── __init__.py │ ├── __openerp__.py │ ├── action │ │ └── goods_action.xml │ ├── goods.py │ ├── menu │ │ └── goods_menu.xml │ └── view │ │ └── goods_view.xml ├── money │ ├── __init__.py │ ├── __openerp__.py │ ├── data │ │ ├── money_data.xml │ │ └── money_sequence.xml │ ├── money_order.py │ ├── money_transfer_order.py │ ├── other_money_order.py │ ├── report │ │ ├── __init__.py │ │ ├── bank_statements.py │ │ ├── bank_statements_view.xml │ │ ├── other_money_statements.py │ │ ├── other_money_statements_view.xml │ │ ├── partner_statements.py │ │ ├── partner_statements_view.xml │ │ ├── print.xml │ │ └── print_money_order.xml │ ├── view │ │ ├── money_order_view.xml │ │ ├── money_transfer_order_view.xml │ │ ├── other_money_order_view.xml │ │ └── reconcile_order_view.xml │ └── wizard │ │ ├── __init__.py │ │ ├── bank_statements_wizard.py │ │ ├── bank_statements_wizard_view.xml │ │ ├── other_money_statements_wizard.py │ │ ├── other_money_statements_wizard_view.xml │ │ ├── partner_statements_wizard.py │ │ └── partner_statements_wizard_view.xml ├── sell │ ├── __init__.py │ ├── __openerp__.py │ ├── sell.py │ ├── sell_data.xml │ └── sell_view.xml └── warehouse │ ├── __init__.py │ ├── __openerp__.py │ ├── action │ └── warehouse_action.xml │ ├── data │ ├── sequence.xml │ └── warehouse_data.xml │ ├── goods.py │ ├── html │ └── move_lot.html │ ├── inventory.py │ ├── menu │ └── warehouse_menu.xml │ ├── move_matching.py │ ├── production.py │ ├── report │ ├── __init__.py │ ├── lot_status.py │ ├── lot_status_view.xml │ ├── lot_track.py │ ├── lot_track_view.xml │ ├── report_base.py │ ├── stock_balance.py │ ├── stock_balance_view.xml │ ├── stock_transceive.py │ ├── stock_transceive_collect.py │ ├── stock_transceive_collect_view.xml │ └── stock_transceive_view.xml │ ├── static │ └── src │ │ ├── css │ │ └── style.css │ │ └── js │ │ └── warehouse_widget.js │ ├── utils.py │ ├── view │ ├── assets_backend.xml │ ├── goods_view.xml │ ├── inventory_view.xml │ ├── production_view.xml │ └── warehouse_view.xml │ ├── warehouse.py │ ├── warehouse_move.py │ ├── warehouse_move_line.py │ ├── warehouse_order.py │ └── wizard │ ├── __init__.py │ ├── lot_track_wizard.py │ ├── lot_track_wizard_view.xml │ ├── save_bom.py │ ├── save_bom_view.xml │ ├── stock_transceive_collect_wizard.py │ ├── stock_transceive_collect_wizard_view.xml │ ├── stock_transceive_wizard.py │ └── stock_transceive_wizard_view.xml └── extra ├── web_editable_list_length ├── __init__.py ├── __openerp__.py ├── static │ └── src │ │ └── js │ │ └── list.js └── views │ └── assets_backend.xml ├── web_editable_open_dialog ├── __init__.py ├── __openerp__.py ├── static │ └── src │ │ ├── css │ │ └── style.css │ │ └── js │ │ └── dialog.js └── views │ └── assets_backend.xml ├── web_float_limit ├── __init__.py ├── __openerp__.py ├── static │ └── src │ │ └── js │ │ └── limit.js └── views │ └── assets_backend.xml ├── web_menu_create ├── __init__.py ├── __openerp__.py ├── ir_ui_view.py ├── static │ └── src │ │ ├── css │ │ └── style.css │ │ └── js │ │ └── menu.js └── views │ └── assets_backend.xml ├── web_one2many_reconstruction ├── __init__.py ├── __openerp__.py ├── static │ └── src │ │ ├── css │ │ └── style.css │ │ ├── js │ │ └── one2many.js │ │ └── xml │ │ └── one2many.xml └── views │ └── assets_backend.xml ├── web_readonly_bypass ├── README.rst ├── __init__.py ├── __openerp__.py ├── static │ ├── src │ │ └── js │ │ │ └── readonly_bypass.js │ └── test │ │ └── web_readonly_bypass.js └── views │ └── readonly_bypass.xml └── web_sublist ├── __init__.py ├── __openerp__.py ├── static └── src │ ├── css │ └── style.css │ ├── js │ └── sublist.js │ └── xml │ └── sublist.xml └── views └── assets_backend.xml /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | csslint: 3 | enabled: true 4 | pep8: 5 | enabled: true 6 | eslint: 7 | enabled: true 8 | FIXME: 9 | enabled: true 10 | duplication: 11 | enabled: false 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | .idea 3 | .idea/* 4 | .project 5 | .pydevproject 6 | .settings/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [在线试用](http://gooderp.osbzr.net/login?db=gooderp&login=admin&key=good) 2 | ====== 3 | 4 | [](https://gitter.im/osbzr/gooderp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | [](https://travis-ci.org/osbzr/gooderp) 6 | [](https://coveralls.io/github/osbzr/gooderp?branch=master) 7 | [](https://codeclimate.com/github/osbzr/gooderp) 8 | 9 | 开阖软件发起的开源ERP项目 10 | 11 | 如果你有一个苹果,我也有一个苹果,彼此交换后,你我还是一人一个苹果,但是如果你有一个想法,我有一个想法,彼此交换后,你我就都有两个想法,三个人呢?一百个人呢? 12 | 13 | 使用openobject框架 14 | 15 | 重写全部功能模块 16 | 17 | 18 | Why——为什么要做GOODERP 19 | --------------------- 20 | 1、OpenERP面向最终用户,GOODERP面向实施公司 21 | 22 | 2、OpenERP项目由openerp公司主导,GOODERP项目由实施公司主导 23 | 24 | 3、提高核心功能模块的稳定性和易用性,降低标准功能部署成本 25 | 26 | 4、针对现有成熟产品重新组织功能设计,使GOODERP有清晰的市场定位和竞争对手 27 | 28 | 5、实行开源项目贡献者奖励制度,让开源成为众包 29 | 30 | 6、参照现有ERP软件构建业务伙伴支持网络和实施工具包 31 | 32 | 7、通过大量读写代码培养和发现具备openobject平台二次开发能力的程序员 33 | 34 | 35 | What——关于GOODERP产品 36 | -------------------- 37 | 1、GOODERP是托管在github上的一个开源ERP项目 38 | 39 | 2、软件采用agpl协议,版权归代码提交者所有 40 | 41 | 3、项目范围是一组功能模块,包括财务加进销存的核心模块及满足行业特殊需求的模块 42 | 43 | 4、这些模块都以openobject8.0为平台开发 44 | 45 | 5、模块全部放在 osbzr/gooderp mater分支的根目录下,每个模块一个目录 46 | 47 | 6、参照 ys 的功能菜单和输出布局重新设计 48 | 49 | 7、项目本身不提供下载服务,上传下载均通过github版本管理工具 50 | 51 | 52 | Who——谁来做GOODERP项目 53 | --------------------- 54 | 1、项目经理:上海开阖软件有限公司 王剑峰 55 | 56 | 2、项目投资人:GOODERP认证业务伙伴 gooderp-partner 57 | 58 | 3、项目成员:任何人均可克隆、修改、提交合并请求 59 | 60 | 4、项目经理负责协调业务伙伴与贡献者关系 61 | 62 | 5、项目投资人负责审批分支合并请求,每月评定顶尖贡献者。 63 | 64 | 6、项目成员报告bug、通过提交分支合并请求的方式向项目贡献代码 65 | 66 | 67 | When——GOODERP项目的时间规划 68 | -------------------------- 69 | 1、项目启动日期2016年2月22日 70 | 71 | 2、第一阶段,2016年,完成财务+进销存+项目管理的核心功能 72 | 73 | 3、第二阶段,长期规划,根据客户项目和业务伙伴需求实现各行业纵深功能 74 | 75 | 4、每月定期(日期待定)举行业务伙伴会议,总结上月进度,评选最佳贡献者,计划下月工作 76 | 77 | 5、业务伙伴资格有效期为1年 78 | 79 | 6、项目实行7*24小时工作制,全年无休 80 | 81 | 7、项目以一个自然月为一个计划交付周期 82 | 83 | Where——使用github管理GOODERP开发 84 | ------------------------------- 85 | 86 | 1、快 87 | 88 | 2、程序员最爱 89 | 90 | 3、贡献代码方便 91 | 92 | 4、免费 93 | 94 | 5、不断优化 95 | 96 | 6、一站解决 97 | 98 | 7、在线沟通协作 99 | 100 | How——如何让GOODERP持续健康发展 101 | ----------------------------- 102 | 1、投资者应该参与决策 103 | 104 | 2、贡献者必须得到认可 105 | 106 | 3、现金回报及时到位 107 | 108 | 4、关注业务伙伴的需求,而非最终用户 109 | 110 | 5、搭建在线测试服务器 111 | 112 | 6、鼓励非程序员参与测试,特别是ys现有用户 113 | 114 | 7、开展多种双赢合作模式 115 | 116 | 开发环境准备 117 | ------------- 118 | 1.在github上fork点击右上角的fork 119 | 120 | 2.clone到本地 121 | 122 | git clone https://github.com/你的名字/gooderp.git 123 | 124 | 3.增加远程分支(也就是osbzr的分支)名为osbzr到你本地。 125 | 126 | git remote add osbzr https://github.com/osbzr/gooderp.git 127 | 128 | 环境就准备好了 129 | 130 | 131 | 把远程分支的合并到自己的分支 132 | ---------------------------- 133 | 1.把对方的代码拉到你本地。 134 | 135 | git fetch osbzr 136 | 137 | 2.合并对方代码 138 | 139 | git merge osbzr/master 140 | 141 | 3.最新的代码推送到你的github上。 142 | 143 | git push origin master 144 | 145 | 当本地代码写好要提交到主干项目 146 | ------------------------------- 147 | 1.添加要提交的目录 148 | 149 | git add . 150 | 151 | 2.提交更新 152 | 153 | git commit -m"本次修改的描述" 154 | 155 | 3.推送到github 156 | 157 | git push 158 | 159 | 4.在github上点击pull request按钮 160 | -------------------------------------------------------------------------------- /addons/buy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import buy -------------------------------------------------------------------------------- /addons/buy/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | { 3 | 'name': "GOODERP 采购模块", 4 | 'author': "flora@osbzr.com", 5 | 'website': "http://www.osbzr.com", 6 | 'category': 'gooderp', 7 | 'version': '8.0.0.1', 8 | 'depends': ['core','mail','warehouse','money'], 9 | 'data': [ 10 | 'buy_view.xml', 11 | 'buy_data.xml', 12 | ] 13 | } -------------------------------------------------------------------------------- /addons/buy/buy_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Buy Order 7 | buy.order 8 | 9 | 10 | Buy Order 11 | buy.order 12 | BO 13 | 5 14 | 15 | 16 | 17 | 18 | 19 | Buy Receipt 20 | buy.receipt 21 | 22 | 23 | Buy Receipt 24 | buy.receipt 25 | WH/IN/ 26 | 5 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /addons/core/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import core 3 | -------------------------------------------------------------------------------- /addons/core/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | { 3 | 'name': "GOODERP 核心模块", 4 | 'author': "开阖软件", 5 | 'website': "http://www.osbzr.com", 6 | 'category': 'gooderp', 7 | 'version': '8.0.0.1', 8 | 'depends': [], 9 | 'data': [ 10 | 'core_view.xml', 11 | ] 12 | } -------------------------------------------------------------------------------- /addons/core/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp import models, fields, api 4 | 5 | CORE_CATEGORY_TYPE = [('customer',u'客户'), 6 | ('supplier',u'供应商'), 7 | ('goods',u'商品'), 8 | ('expense',u'支出'), 9 | ('income',u'收入'), 10 | ('other_pay',u'其他支出'), 11 | ('other_get',u'其他收入'), 12 | ('attribute',u'属性'), 13 | ('goods',u'产品')] 14 | CORE_COST_METHOD = [('average',u'移动平均法'), 15 | ('fifo',u'先进先出法'), 16 | ] 17 | 18 | class core_value(models.Model): 19 | _name = 'core.value' 20 | name = fields.Char(u'名称') 21 | type = fields.Char(u'类型', default=lambda self: self._context.get('type')) 22 | 23 | class core_category(models.Model): 24 | _name = 'core.category' 25 | name = fields.Char(u'名称') 26 | type = fields.Selection(CORE_CATEGORY_TYPE,u'类型',default=lambda self: self._context.get('type')) 27 | 28 | class res_company(models.Model): 29 | _inherit = 'res.company' 30 | start_date = fields.Date(u'启用日期') 31 | quantity_digits = fields.Integer(u'数量小数位') 32 | amount_digits = fields.Integer(u'单价小数位') 33 | cost_method = fields.Selection(CORE_COST_METHOD,u'存货计价方法') 34 | negtive_quantity = fields.Boolean(u'是否检查负库存') 35 | draft_invoice = fields.Boolean(u'根据发票确认应收应付') 36 | 37 | class uom(models.Model): 38 | _name = 'uom' 39 | name = fields.Char(u'名称') 40 | 41 | class settle_mode(models.Model): 42 | _name = 'settle.mode' 43 | name = fields.Char(u'名称') 44 | 45 | class partner(models.Model): 46 | _name = 'partner' 47 | code = fields.Char(u'编号') 48 | name = fields.Char(u'名称') 49 | c_category_id = fields.Many2one('core.category',u'客户类别', 50 | domain=[('type','=','customer')],context={'type':'customer'}) 51 | s_category_id = fields.Many2one('core.category',u'供应商类别', 52 | domain=[('type','=','supplier')],context={'type':'supplier'}) 53 | receivable = fields.Float(u'应收余额') 54 | payable = fields.Float(u'应付余额') 55 | 56 | class goods(models.Model): 57 | _name = 'goods' 58 | code = fields.Char(u'编号') 59 | name = fields.Char(u'名称') 60 | category_id = fields.Many2one('core.category',u'产品类别', 61 | domain=[('type','=','goods')],context={'type':'goods'}) 62 | uom_id = fields.Many2one('uom',u'计量单位') 63 | uos_id = fields.Many2one('uom',u'辅助单位') 64 | conversion = fields.Float(u'转化率(1辅助单位等于多少计量单位)') 65 | cost = fields.Float(u'成本') 66 | price_ids = fields.One2many('goods.price','goods_id',u'价格清单') 67 | 68 | class goods_price(models.Model): 69 | _name = 'goods.price' 70 | goods_id = fields.Many2one('goods','商品') 71 | category_id = fields.Many2one('core.category',u'客户类别', 72 | domain=[('type','=','customer')],context={'type':'customer'}) 73 | price = fields.Float(u'价格') 74 | 75 | class warehouse(models.Model): 76 | _name = 'warehouse' 77 | name = fields.Char(u'名称') 78 | 79 | class staff(models.Model): 80 | _name = 'staff' 81 | name = fields.Char(u'名称') 82 | 83 | class bank_account(models.Model): 84 | _name = 'bank.account' 85 | name = fields.Char(u'名称') 86 | balance = fields.Float(u'余额') 87 | -------------------------------------------------------------------------------- /addons/demo/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /addons/goods/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import goods 3 | -------------------------------------------------------------------------------- /addons/goods/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # OpenERP, Open Source Management Solution 5 | # Copyright (C) 2013-Today OpenERP SA (). 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as 9 | # published by the Free Software Foundation, either version 3 of the 10 | # License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################## 21 | 22 | { 23 | "name": "GOODERP Goods Management", 24 | "version": "0.1", 25 | "author": 'ZhengXiang', 26 | "website": "http://www.osbzr.com", 27 | "category": "Generic Modules", 28 | "depends": ['core', 'decimal_precision'], 29 | "description": """ 30 | """, 31 | "data": [ 32 | 'view/goods_view.xml', 33 | 'action/goods_action.xml', 34 | 'menu/goods_menu.xml', 35 | ], 36 | 'installable': True, 37 | "active": False, 38 | } 39 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 40 | -------------------------------------------------------------------------------- /addons/goods/action/goods_action.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 产品 6 | goods 7 | tree,form 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /addons/goods/goods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp import models, fields, api 4 | 5 | 6 | class goods(models.Model): 7 | _inherit = 'goods' 8 | 9 | using_batch = fields.Boolean(u'批次管理') 10 | force_batch_one = fields.Boolean(u'每批次数量为1') 11 | attribute_ids = fields.One2many('attribute', 'goods_id', string=u'属性') 12 | 13 | 14 | class attribute(models.Model): 15 | _name = 'attribute' 16 | 17 | @api.one 18 | @api.depends('value_ids') 19 | def _compute_name(self): 20 | self.name = ' '.join([value.value_id.name for value in self.value_ids]) 21 | 22 | name = fields.Char(u'名称', compute='_compute_name', store=True,readonly=True) 23 | goods_id = fields.Many2one('goods', u'商品') 24 | value_ids = fields.One2many('attribute.value', 'attribute_id', string=u'属性') 25 | 26 | 27 | class attribute_value(models.Model): 28 | _name = 'attribute.value' 29 | _rec_name = 'value_id' 30 | attribute_id = fields.Many2one('attribute',u'属性') 31 | category_id = fields.Many2one('core.category',u'属性', 32 | domain=[('type','=','attribute')],context={'type':'attribute'} 33 | ,required='1') 34 | value_id = fields.Many2one('attribute.value.value',u'值', 35 | domain="[('category_id','=',category_id)]" 36 | ,required='1') 37 | class attribute_value(models.Model): 38 | _name = 'attribute.value.value' 39 | category_id = fields.Many2one('core.category',u'属性', 40 | domain=[('type','=','attribute')],context={'type':'attribute'} 41 | ,required='1') 42 | name = fields.Char(u'值') 43 | -------------------------------------------------------------------------------- /addons/goods/menu/goods_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /addons/goods/view/goods_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | goods.tree 6 | goods 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | goods.form 18 | goods 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | attribute.form 61 | attribute 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /addons/money/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import money_order 3 | import other_money_order 4 | import money_transfer_order 5 | import report 6 | import wizard -------------------------------------------------------------------------------- /addons/money/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | { 3 | 'name': "GOODERP 会计模块", 4 | 'author': "judy@osbzr.com", 5 | 'website': "http://www.osbzr.com", 6 | 'category': 'gooderp', 7 | 'version': '8.0.0.1', 8 | 'depends': ['core','base'], 9 | 'data': [ 10 | 'view/money_order_view.xml', 11 | 'view/other_money_order_view.xml', 12 | 'view/money_transfer_order_view.xml', 13 | 'view/reconcile_order_view.xml', 14 | 'data/money_sequence.xml', 15 | 'data/money_data.xml', 16 | 'report/partner_statements_view.xml', 17 | 'wizard/partner_statements_wizard_view.xml', 18 | 'report/bank_statements_view.xml', 19 | 'wizard/bank_statements_wizard_view.xml', 20 | 'report/other_money_statements_view.xml', 21 | 'wizard/other_money_statements_wizard_view.xml', 22 | ] 23 | } -------------------------------------------------------------------------------- /addons/money/data/money_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 销售 7 | income 8 | 9 | 10 | 11 | 采购 12 | expense 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /addons/money/data/money_sequence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 收款单 7 | get.order 8 | 9 | 10 | 收款单 11 | get.order 12 | GET%(year)s 13 | 5 14 | 15 | 16 | 17 | 18 | 付款单 19 | pay.order 20 | 21 | 22 | 付款单 23 | pay.order 24 | PAY%(year)s 25 | 5 26 | 27 | 28 | 29 | 30 | 其他收入单 31 | other.get.order 32 | 33 | 34 | 其他收入单 35 | other.get.order 36 | OTHER_GET%(year)s 37 | 5 38 | 39 | 40 | 41 | 42 | 其他支出单 43 | other.pay.order 44 | 45 | 46 | 其他支出单 47 | other.pay.order 48 | OTHER_PAY%(year)s 49 | 5 50 | 51 | 52 | 53 | 54 | 资金转账单 55 | money.transfer.order 56 | 57 | 58 | 资金转账单 59 | money.transfer.order 60 | TR%(year)s 61 | 5 62 | 63 | 64 | 65 | 66 | 核销单 67 | reconcile.order 68 | 69 | 70 | 核销单 71 | reconcile.order 72 | RO%(year)s/ 73 | 5 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /addons/money/money_transfer_order.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # Copyright (C) 2016 开阖软件(). 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as 8 | # published by the Free Software Foundation, either version 3 of the 9 | # License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | # 19 | ############################################################################## 20 | 21 | from openerp.exceptions import except_orm 22 | from openerp import fields, models, api 23 | 24 | class money_transfer_order(models.Model): 25 | _name = 'money.transfer.order' 26 | _description = u'资金转账单' 27 | 28 | @api.model 29 | def create(self, values): 30 | print self._name 31 | if values.get('name', '/') == '/': 32 | values.update({'name': self.env['ir.sequence'].get(self._name) or '/'}) 33 | 34 | return super(money_transfer_order, self).create(values) 35 | 36 | state = fields.Selection([ 37 | ('draft', u'未审核'), 38 | ('done', u'已审核'), 39 | ], string=u'状态', readonly=True, default='draft', copy=False) 40 | name = fields.Char(string=u'单据编号', copy=False, default='/') 41 | date = fields.Date(string=u'单据日期', default=lambda self: fields.Date.context_today(self), readonly=True, states={'draft': [('readonly', False)]}) 42 | note = fields.Text(string=u'备注', readonly=True, states={'draft': [('readonly', False)]}) 43 | line_ids = fields.One2many('money.transfer.order.line', 'transfer_id', string=u'资金转账单行', readonly=True, states={'draft': [('readonly', False)]}) 44 | 45 | @api.multi 46 | def money_transfer_done(self): 47 | '''转账单的审核按钮''' 48 | for transfer in self: 49 | if not transfer.line_ids: 50 | raise except_orm('错误','请先输入转账金额') 51 | if transfer.line_ids.out_bank_id == transfer.line_ids.in_bank_id: 52 | raise except_orm('错误','转出账户与转入账户不能相同') 53 | for line in transfer.line_ids: 54 | if line.amount < 0: 55 | raise except_orm('错误','转账金额必须大于0') 56 | if line.out_bank_id.balance < line.amount: 57 | raise except_orm('错误','转出账户余额不足') 58 | else: 59 | line.out_bank_id.balance -= line.amount 60 | line.in_bank_id.balance += line.amount 61 | transfer.state = 'done' 62 | return True 63 | 64 | @api.multi 65 | def money_transfer_draft(self): 66 | '''转账单的反审核按钮''' 67 | for transfer in self: 68 | for line in transfer.line_ids: 69 | if line.in_bank_id.balance < line.amount: 70 | raise except_orm('错误','转入账户余额不足') 71 | else: 72 | line.in_bank_id.balance -= line.amount 73 | line.out_bank_id.balance += line.amount 74 | transfer.state = 'draft' 75 | return True 76 | 77 | class money_transfer_order_line(models.Model): 78 | _name = 'money.transfer.order.line' 79 | _description = u'资金转账单明细' 80 | 81 | transfer_id = fields.Many2one('money.transfer.order', string=u'资金转账单') 82 | out_bank_id = fields.Many2one('bank.account', string=u'转出账户', required=True) 83 | in_bank_id = fields.Many2one('bank.account', string=u'转入账户', required=True) 84 | amount = fields.Float(string=u'金额') 85 | mode_id = fields.Many2one('settle.mode', string=u'结算方式') 86 | number = fields.Char(string=u'结算号') 87 | note = fields.Char(string=u'备注') -------------------------------------------------------------------------------- /addons/money/other_money_order.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # Copyright (C) 2016 开阖软件(). 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as 8 | # published by the Free Software Foundation, either version 3 of the 9 | # License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | # 19 | ############################################################################## 20 | 21 | from openerp.exceptions import except_orm 22 | from openerp import fields, models, api 23 | 24 | class other_money_order(models.Model): 25 | _name = 'other.money.order' 26 | _description = u'其他收入/其他支出' 27 | 28 | TYPE_SELECTION = [ 29 | ('other_pay', u'其他收入'), 30 | ('other_get', u'其他支出'), 31 | ] 32 | 33 | @api.model 34 | def create(self, values): 35 | # 创建单据时,更新订单类型的不同,生成不同的单据编号 36 | if self._context.get('type') == 'other_get': 37 | values.update({'name': self.env['ir.sequence'].get('other.get.order') or '/'}) 38 | if self._context.get('type') == 'other_pay' or values.get('name', '/') == '/': 39 | values.update({'name': self.env['ir.sequence'].get('other.pay.order') or '/'}) 40 | 41 | return super(other_money_order, self).create(values) 42 | 43 | @api.one 44 | @api.depends('line_ids.amount') 45 | def _compute_total_amount(self): 46 | # 计算应付金额/应收金额 47 | self.total_amount = sum(line.amount for line in self.line_ids) 48 | 49 | state = fields.Selection([ 50 | ('draft', u'未审核'), 51 | ('done', u'已审核'), 52 | ], string=u'状态', readonly=True, default='draft', copy=False) 53 | partner_id = fields.Many2one('partner', string=u'往来单位', readonly=True, states={'draft': [('readonly', False)]}) 54 | date = fields.Date(string=u'单据日期', default=lambda self: fields.Date.context_today(self), readonly=True, states={'draft': [('readonly', False)]}) 55 | name = fields.Char(string=u'单据编号', copy=False, readonly=True, default='/') 56 | total_amount = fields.Float(string=u'金额', compute='_compute_total_amount', store=True, readonly=True) 57 | bank_id = fields.Many2one('bank.account', string=u'结算账户',required=True, readonly=True, states={'draft': [('readonly', False)]}) 58 | line_ids = fields.One2many('other.money.order.line', 'other_money_id', string=u'收支单行', readonly=True, states={'draft': [('readonly', False)]}) 59 | type = fields.Selection(TYPE_SELECTION, string=u'类型', default=lambda self: self._context.get('type'), readonly=True, states={'draft': [('readonly', False)]}) 60 | 61 | @api.onchange('partner_id') 62 | def _onchange_partner(self): 63 | ''' 64 | 根据所选业务伙伴源单填充行 65 | ''' 66 | self.line_ids = [] 67 | lines = [] 68 | for invoice in self.env['money.invoice'].search([('partner_id','=',self.partner_id.id),('to_reconcile','>',0)]): 69 | lines.append((0,0,{ 70 | 'category_id':invoice.category_id.id, 71 | 'source_id':invoice.id, 72 | 'amount':invoice.to_reconcile, 73 | })) 74 | self.line_ids = lines 75 | 76 | @api.multi 77 | def other_money_done(self): 78 | '''其他收支单的审核按钮''' 79 | for other in self: 80 | if other.total_amount <= 0: 81 | raise except_orm(u'错误', u'金额应该大于0') 82 | for line in other.line_ids: 83 | # 针对源单付款,则更新源单和供应商应付 84 | if line.source_id: 85 | if line.amount > line.source_id.to_reconcile: 86 | raise except_orm(u'错误', u'核销金额大于源单未核销金额') 87 | else: 88 | line.source_id.to_reconcile -= line.amount 89 | other.partner_id.payable -= line.amount 90 | # 根据单据类型更新账户余额 91 | if other.type == 'other_pay': 92 | other.bank_id.balance -= other.total_amount 93 | else: 94 | other.bank_id.balance += other.total_amount 95 | other.state = 'done' 96 | return True 97 | 98 | @api.multi 99 | def other_money_draft(self): 100 | '''其他收支单的反审核按钮''' 101 | for other in self: 102 | for line in other.line_ids: 103 | # 针对源单付款,则更新源单和供应商应付 104 | if line.source_id: 105 | line.source_id.to_reconcile += line.amount 106 | other.partner_id.payable += line.amount 107 | # 根据单据类型更新账户余额 108 | if other.type == 'other_pay': 109 | other.bank_id.balance += other.total_amount 110 | else: 111 | other.bank_id.balance -= other.total_amount 112 | other.state = 'draft' 113 | return True 114 | 115 | @api.multi 116 | def print_other_money_order(self): 117 | '''打印 其他收入/支出单''' 118 | assert len(self._ids) == 1, '一次执行只能有一个id' 119 | return self.env['report'].get_action('money.report_other_money_order') 120 | 121 | class other_money_order_line(models.Model): 122 | _name = 'other.money.order.line' 123 | _description = u'其他收支单明细' 124 | 125 | other_money_id = fields.Many2one('other.money.order', string=u'其他收支') 126 | category_id = fields.Many2one('core.category', u'类别', domain="[('type', '=', context.get('type'))]") 127 | source_id = fields.Many2one('money.invoice', string=u'源单') 128 | amount = fields.Float(string=u'金额') 129 | note = fields.Char(string=u'备注') 130 | -------------------------------------------------------------------------------- /addons/money/report/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import partner_statements 3 | import bank_statements 4 | import other_money_statements -------------------------------------------------------------------------------- /addons/money/report/bank_statements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from openerp import fields, models, api, tools 3 | 4 | class bank_statements_report(models.Model): 5 | _name = "bank.statements.report" 6 | _description = u"现金银行报表" 7 | _auto = False 8 | _order = 'date' 9 | 10 | @api.one 11 | @api.depends('get', 'pay', 'bank_id') 12 | def _compute_balance(self): 13 | # 相邻的两条记录,bank_id不同,重新计算账户余额 14 | pre_record = self.search([('id', '=', self.id - 1), ('bank_id', '=', self.bank_id.id)]) 15 | if pre_record: 16 | before_balance = pre_record.balance 17 | else: 18 | before_balance = 0 19 | self.balance += before_balance + self.get - self.pay 20 | 21 | bank_id = fields.Many2one('bank.account', string=u'账户名称', readonly=True) 22 | date = fields.Date(string=u'日期', readonly=True) 23 | name = fields.Char(string=u'单据编号', readonly=True) 24 | get = fields.Float(string=u'收入', readonly=True) 25 | pay = fields.Float(string=u'支出', readonly=True) 26 | balance = fields.Float(string=u'账户余额', compute='_compute_balance', readonly=True) 27 | partner_id = fields.Many2one('partner', string=u'往来单位', readonly=True) 28 | note = fields.Char(string=u'备注', readonly=True) 29 | 30 | def init(self, cr): 31 | # union money_order, other_money_order, money_transfer_order 32 | tools.drop_view_if_exists(cr, 'bank_statements_report') 33 | cr.execute(""" 34 | CREATE or REPLACE VIEW bank_statements_report AS ( 35 | SELECT ROW_NUMBER() OVER(ORDER BY bank_id,date) AS id, 36 | bank_id, 37 | date, 38 | name, 39 | get, 40 | pay, 41 | balance, 42 | partner_id, 43 | note 44 | FROM 45 | (SELECT mol.bank_id, 46 | mo.date, 47 | mo.name, 48 | (CASE WHEN mo.type = 'get' THEN mol.amount ELSE 0 END) AS get, 49 | (CASE WHEN mo.type = 'pay' THEN mol.amount ELSE 0 END) AS pay, 50 | 0 AS balance, 51 | mo.partner_id, 52 | mol.note 53 | FROM money_order_line AS mol 54 | LEFT JOIN money_order AS mo ON mol.money_id = mo.id 55 | UNION ALL 56 | SELECT omo.bank_id, 57 | omo.date, 58 | omo.name, 59 | (CASE WHEN omo.type = 'other_get' THEN omo.total_amount ELSE 0 END) AS get, 60 | (CASE WHEN omo.type = 'other_pay' THEN omo.total_amount ELSE 0 END) AS pay, 61 | 0 AS balance, 62 | omo.partner_id, 63 | NULL AS note 64 | FROM other_money_order AS omo 65 | UNION ALL 66 | SELECT mtol.out_bank_id AS bank_id, 67 | mto.date, 68 | mto.name, 69 | 0 AS get, 70 | mtol.amount AS pay, 71 | 0 AS balance, 72 | NULL AS partner_id, 73 | mto.note 74 | FROM money_transfer_order_line AS mtol 75 | LEFT JOIN money_transfer_order AS mto ON mtol.transfer_id = mto.id 76 | UNION ALL 77 | SELECT mtol.in_bank_id AS bank_id, 78 | mto.date, 79 | mto.name, 80 | mtol.amount AS get, 81 | 0 AS pay, 82 | 0 AS balance, 83 | NULL AS partner_id, 84 | mto.note 85 | FROM money_transfer_order_line AS mtol 86 | LEFT JOIN money_transfer_order AS mto ON mtol.transfer_id = mto.id 87 | ) AS bs) 88 | """) 89 | 90 | @api.multi 91 | def find_source_order(self): 92 | # 查看源单,三种情况:收付款单、其他收支单、资金转换单 93 | money = self.env['money.order'].search([('name', '=', self.name)]) 94 | other_money = self.env['other.money.order'].search([('name', '=', self.name)]) 95 | 96 | if money: 97 | view = self.env.ref('money.money_order_form') 98 | return { 99 | 'name': u'收付款单', 100 | 'view_type': 'form', 101 | 'view_mode': 'form', 102 | 'view_id': False, 103 | 'views': [(view.id, 'form')], 104 | 'res_model': 'money.order', 105 | 'type': 'ir.actions.act_window', 106 | 'res_id': money.id 107 | } 108 | elif other_money: 109 | view = self.env.ref('money.other_money_order_form') 110 | return { 111 | 'name': u'其他收支单', 112 | 'view_type': 'form', 113 | 'view_mode': 'form', 114 | 'view_id': False, 115 | 'views': [(view.id, 'form')], 116 | 'res_model': 'other.money.order', 117 | 'type': 'ir.actions.act_window', 118 | 'res_id': other_money.id, 119 | 'context': {'type': False} 120 | } 121 | 122 | transfer_order = self.env['money.transfer.order'].search([('name', '=', self.name)]) 123 | view = self.env.ref('money.money_transfer_order_form') 124 | 125 | return { 126 | 'name': u'资金转换单', 127 | 'view_type': 'form', 128 | 'view_mode': 'form', 129 | 'view_id': False, 130 | 'views': [(view.id, 'form')], 131 | 'res_model': 'money.transfer.order', 132 | 'type': 'ir.actions.act_window', 133 | 'res_id': transfer_order.id 134 | } 135 | 136 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 137 | -------------------------------------------------------------------------------- /addons/money/report/bank_statements_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | bank.statements.report.tree 7 | bank.statements.report 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /addons/money/report/other_money_statements.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp import fields, models, api, tools 4 | 5 | class other_money_statements_report(models.Model): 6 | _name = "other.money.statements.report" 7 | _description = u"其他收支明细表" 8 | _auto = False 9 | 10 | date = fields.Date(string=u'日期', readonly=True) 11 | name = fields.Char(string=u'单据编号', readonly=True) 12 | type = fields.Char(string=u'类别', readonly=True) 13 | category_id = fields.Many2one('core.category', string=u'收支项目', readonly=True) 14 | get = fields.Float(string=u'收入', readonly=True) 15 | pay = fields.Float(string=u'支出', readonly=True) 16 | partner_id = fields.Many2one('partner', string=u'往来单位', readonly=True) 17 | note = fields.Char(string=u'备注', readonly=True) 18 | 19 | def init(self, cr): 20 | # select other_money_order_line、other_money_order 21 | tools.drop_view_if_exists(cr, 'other_money_statements_report') 22 | cr.execute(""" 23 | CREATE or REPLACE VIEW other_money_statements_report AS ( 24 | SELECT omol.id, 25 | omo.date, 26 | omo.name, 27 | (CASE WHEN omo.type = 'other_get' THEN '其他收入' ELSE '其他支出' END) AS type, 28 | omol.category_id, 29 | (CASE WHEN omo.type = 'other_get' THEN omol.amount ELSE 0 END) AS get, 30 | (CASE WHEN omo.type = 'other_pay' THEN omol.amount ELSE 0 END) AS pay, 31 | omo.partner_id, 32 | omol.note 33 | FROM other_money_order_line AS omol 34 | LEFT JOIN other_money_order AS omo ON omol.other_money_id = omo.id 35 | ORDER BY date) 36 | """) 37 | 38 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 39 | -------------------------------------------------------------------------------- /addons/money/report/other_money_statements_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | other.money.statements.report.tree 7 | other.money.statements.report 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /addons/money/report/partner_statements_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | customer.statements.report.tree 7 | customer.statements.report 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | supplier.statements.report.tree 27 | supplier.statements.report 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | customer.statements.report.with.goods.tree 46 | partner.statements.report.with.goods 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | supplier.statements.report.with.goods.tree 75 | partner.statements.report.with.goods 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /addons/money/report/print.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /addons/money/report/print_money_order.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 其他收入单 11 | 客户名称: 12 | 单据日期: 13 | 单据编号: 14 | 15 | 16 | 17 | 18 | 收入类别 19 | 金额 20 | 备注 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 税前小计 41 | 42 | 43 | 44 | 45 | 46 | 税 47 | 48 | 49 | 50 | 51 | 52 | 合计 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 支付条款: 62 | 开票说明: 63 | 64 | 65 | 纸质发票号:__________________________ 66 | 销售员: 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /addons/money/view/money_transfer_order_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | money.transfer.order.tree 7 | money.transfer.order 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | money.transfer.order.form 20 | money.transfer.order 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 资金转账单 67 | ir.actions.act_window 68 | money.transfer.order 69 | tree,form 70 | 71 | 72 | 点击创建资金转账单 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /addons/money/view/other_money_order_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | other.money.order.tree 7 | other.money.order 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | other.money.order.form 22 | other.money.order 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 其他收入单 77 | ir.actions.act_window 78 | other.money.order 79 | tree,form 80 | {'type':'other_get'} 81 | [('type','=','other_get')] 82 | 83 | 84 | 点击创建其他收入单 85 | 86 | 87 | 88 | 89 | 90 | 91 | 其他支出单 92 | ir.actions.act_window 93 | other.money.order 94 | tree,form 95 | {'type':'other_pay'} 96 | [('type','=','other_pay')] 97 | 98 | 99 | 点击创建其他支出单 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /addons/money/view/reconcile_order_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | reconcile.order.tree 7 | reconcile.order 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | reconcile.order.form 20 | reconcile.order 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 核销单 85 | ir.actions.act_window 86 | reconcile.order 87 | tree,form 88 | 89 | 90 | 点击创建核销单 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /addons/money/wizard/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import partner_statements_wizard 3 | import bank_statements_wizard 4 | import other_money_statements_wizard -------------------------------------------------------------------------------- /addons/money/wizard/bank_statements_wizard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from openerp.exceptions import except_orm 3 | from openerp import fields, models, api 4 | 5 | class partner_statements_report_wizard(models.Model): 6 | _name = "bank.statements.report.wizard" 7 | _description = u"现金银行报表向导" 8 | 9 | @api.model 10 | def _get_company_start_date(self): 11 | return self.env.user.company_id.start_date 12 | 13 | bank_id = fields.Many2one('bank.account', string=u'账户名称', required=True) 14 | from_date = fields.Date(string=u'开始日期', required=True, default=_get_company_start_date) # 默认公司启用日期 15 | to_date = fields.Date(string=u'结束日期', required=True, default=lambda self: fields.Date.context_today(self)) # 默认当前日期 16 | 17 | @api.multi 18 | def confirm_bank_statements(self): 19 | # 现金银行报表 20 | if self.from_date > self.to_date: 21 | raise except_orm(u'错误!', u'结束日期不能小于开始日期!') 22 | 23 | view = self.env.ref('money.bank_statements_report_tree') 24 | 25 | return { 26 | 'name': u'现金银行报表:' + self.bank_id.name, 27 | 'view_type': 'form', 28 | 'view_mode': 'tree', 29 | 'res_model': 'bank.statements.report', 30 | 'view_id': False, 31 | 'views': [(view.id, 'tree')], 32 | 'type': 'ir.actions.act_window', 33 | 'domain':[('bank_id','=', self.bank_id.id), ('date','>=', self.from_date), ('date','<=', self.to_date)] 34 | } 35 | -------------------------------------------------------------------------------- /addons/money/wizard/bank_statements_wizard_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | bank.statements.report.wizard.form 7 | bank.statements.report.wizard 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 现金银行报表向导 31 | bank.statements.report.wizard 32 | form 33 | form 34 | 35 | new 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /addons/money/wizard/other_money_statements_wizard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from openerp.exceptions import except_orm 3 | from openerp import fields, models, api 4 | 5 | class other_money_statements_report_wizard(models.Model): 6 | _name = "other.money.statements.report.wizard" 7 | _description = u"其他收支明细表向导" 8 | 9 | @api.model 10 | def _get_company_start_date(self): 11 | return self.env.user.company_id.start_date 12 | 13 | from_date = fields.Date(string=u'开始日期', required=True, default=_get_company_start_date) # 默认公司启用日期 14 | to_date = fields.Date(string=u'结束日期', required=True, default=lambda self: fields.Date.context_today(self)) # 默认当前日期 15 | 16 | @api.multi 17 | def confirm_other_money_statements(self): 18 | # 现金银行报表 19 | if self.from_date > self.to_date: 20 | raise except_orm(u'错误!', u'结束日期不能小于开始日期!') 21 | 22 | view = self.env.ref('money.other_money_statements_report_tree') 23 | 24 | return { 25 | 'name': u'其他收支明细表', 26 | 'view_type': 'form', 27 | 'view_mode': 'tree', 28 | 'res_model': 'other.money.statements.report', 29 | 'view_id': False, 30 | 'views': [(view.id, 'tree')], 31 | 'type': 'ir.actions.act_window', 32 | 'domain':[('date','>=', self.from_date), ('date','<=', self.to_date)] 33 | } 34 | -------------------------------------------------------------------------------- /addons/money/wizard/other_money_statements_wizard_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | other.money.statements.report.wizard.form 7 | other.money.statements.report.wizard 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 其他收支明细表向导 30 | other.money.statements.report.wizard 31 | form 32 | form 33 | 34 | new 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /addons/money/wizard/partner_statements_wizard_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | partner.statements.report.wizard.form 7 | partner.statements.report.wizard 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 客户对账单向导 32 | partner.statements.report.wizard 33 | form 34 | form 35 | {'default_customer': True} 36 | new 37 | 38 | 39 | 40 | 41 | 供应商对账单向导 42 | partner.statements.report.wizard 43 | form 44 | form 45 | {'default_supplier': True} 46 | new 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /addons/sell/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sell 3 | -------------------------------------------------------------------------------- /addons/sell/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # OpenERP, Open Source Management Solution 5 | # Copyright (C) 2014 上海开阖软件有限公司 (http://www.osbzr.com). 6 | # All Rights Reserved 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program. If not, see http://www.gnu.org/licenses/. 20 | # 21 | ############################################################################## 22 | { 23 | 'name':'GOODERP 销售模块', 24 | 'author':'jeff@osbzr.com,jacky@osbzr.com', 25 | 'website': 'https://www.osbzr.com', 26 | 'description': '''gooderp销售实例,通过安装gooderp模块展示openerp的销售流程''', 27 | 'depends':['base','mail','core','warehouse'], 28 | 'data':[ 29 | 'sell_view.xml', 30 | 'sell_data.xml', 31 | ], 32 | 'installable': True, 33 | 'auto_install': False, 34 | 'application': True, 35 | } 36 | -------------------------------------------------------------------------------- /addons/sell/sell_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sell Order 7 | sell.order 8 | 9 | 10 | Sell Order 11 | sell.order 12 | SO 13 | 5 14 | 15 | 16 | 17 | 18 | 19 | Sell Delivery 20 | sell.delivery 21 | 22 | 23 | Sell Delivery 24 | sell.delivery 25 | WH/OUT/ 26 | 5 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /addons/warehouse/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import goods 3 | import warehouse 4 | import warehouse_move 5 | import warehouse_order 6 | import inventory 7 | import production 8 | import warehouse_move_line 9 | import move_matching 10 | import wizard 11 | import report 12 | -------------------------------------------------------------------------------- /addons/warehouse/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # OpenERP, Open Source Management Solution 5 | # Copyright (C) 2013-Today OpenERP SA (). 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as 9 | # published by the Free Software Foundation, either version 3 of the 10 | # License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################## 21 | 22 | { 23 | "name": "GOODERP Warehouse Management", 24 | "version": "0.1", 25 | "author": 'ZhengXiang', 26 | "website": "http://www.osbzr.com", 27 | "category": "Generic Modules", 28 | "depends": ['core', 'goods', 'decimal_precision', 'web_readonly_bypass', 'web_sublist', 'web_float_limit'], 29 | "description": """ 30 | """, 31 | "data": [ 32 | 'wizard/save_bom_view.xml', 33 | 'wizard/stock_transceive_wizard_view.xml', 34 | 'wizard/lot_track_wizard_view.xml', 35 | 'wizard/stock_transceive_collect_wizard_view.xml', 36 | 'view/assets_backend.xml', 37 | 'view/warehouse_view.xml', 38 | 'view/inventory_view.xml', 39 | 'view/production_view.xml', 40 | 'view/goods_view.xml', 41 | 'report/stock_balance_view.xml', 42 | 'report/stock_transceive_view.xml', 43 | 'report/stock_transceive_collect_view.xml', 44 | 'report/lot_status_view.xml', 45 | 'report/lot_track_view.xml', 46 | 'action/warehouse_action.xml', 47 | 'menu/warehouse_menu.xml', 48 | 'data/warehouse_data.xml', 49 | 'data/sequence.xml', 50 | ], 51 | 'installable': True, 52 | "active": False, 53 | } 54 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 55 | -------------------------------------------------------------------------------- /addons/warehouse/action/warehouse_action.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 仓库 7 | warehouse 8 | tree 9 | ['|', ('active', '!=', 1), ('active', '=', 1)] 10 | 11 | 12 | 13 | 14 | 出库单 15 | wh.out 16 | tree,form 17 | {'readonly_by_pass': ['goods_qty', 'price', 'subtotal']} 18 | 19 | 20 | 21 | 22 | 入库单 23 | wh.in 24 | tree,form 25 | {'readonly_by_pass': ['goods_qty', 'subtotal']} 26 | 27 | 28 | 29 | 30 | 移库单 31 | wh.internal 32 | tree,form 33 | {'readonly_by_pass': ['goods_qty']} 34 | 35 | 36 | 37 | 38 | 39 | 匹配记录 40 | wh.move.matching 41 | tree 42 | 43 | 44 | 45 | 46 | 47 | 库存调拨 48 | wh.move.line 49 | tree 50 | 51 | 52 | 53 | 54 | 55 | 盘点单 56 | wh.inventory 57 | tree,form 58 | {'readonly_by_pass': ['difference_qty']} 59 | 60 | 61 | 62 | 63 | 64 | 组装单 65 | wh.assembly 66 | tree,form 67 | {'readonly_by_pass': ['price', 'subtotal']} 68 | 69 | 70 | 71 | 72 | 73 | 拆卸单 74 | wh.disassembly 75 | tree,form 76 | {'readonly_by_pass': ['price', 'subtotal']} 77 | 78 | 79 | 80 | 81 | 82 | 模板 83 | wh.bom 84 | tree,form 85 | 86 | 87 | 88 | 89 | 90 | 库存余额表 91 | report.stock.balance 92 | graph,tree 93 | 94 | 95 | 96 | 97 | 98 | 商品收发明细表 99 | report.stock.transceive.wizard 100 | form 101 | new 102 | 103 | 104 | 105 | 106 | 107 | 序列号状态表 108 | report.lot.status 109 | graph,tree 110 | 111 | 112 | 113 | 114 | 115 | 序列号跟踪表 116 | report.lot.track.wizard 117 | new 118 | 119 | 120 | 121 | 122 | 123 | 商品收发汇总表 124 | report.stock.transceive.collect.wizard 125 | form 126 | new 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /addons/warehouse/data/sequence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Account Default Miscellaneous Journal 6 | 7 | MISJ/%(year)s/ 8 | 9 | 10 | 11 | 出库单 12 | wh.out 13 | 14 | 15 | 16 | 出库单 17 | wh.out 18 | 19 | WH/OUT/%(y)s%(month)s 20 | 21 | 22 | 23 | 出库单 24 | wh.in 25 | 26 | 27 | 28 | 入库单 29 | wh.in 30 | 31 | WH/IN/%(y)s%(month)s 32 | 33 | 34 | 35 | 出库单 36 | wh.internal 37 | 38 | 39 | 40 | 移库单 41 | wh.internal 42 | 43 | WH/INT/%(y)s%(month)s 44 | 45 | 46 | 47 | 盘点单 48 | wh.inventory 49 | 50 | 51 | 52 | 盘点单 53 | wh.inventory 54 | 55 | INV/%(y)s%(month)s 56 | 57 | 58 | 59 | 组装单 60 | wh.assembly 61 | 62 | 63 | 64 | 组装单 65 | wh.assembly 66 | 67 | ASS/%(y)s%(month)s 68 | 69 | 70 | 71 | 拆卸单 72 | wh.disassembly 73 | 74 | 75 | 76 | 拆卸单 77 | wh.disassembly 78 | 79 | DIS/%(y)s%(month)s 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /addons/warehouse/data/warehouse_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 供应商仓库 6 | Supplier 7 | supplier 8 | 9 | 10 | 11 | 客户仓库 12 | Customer 13 | customer 14 | 15 | 16 | 17 | 盘点仓库 18 | Inventory 19 | inventory 20 | 21 | 22 | 23 | 生产仓库 24 | Production 25 | production 26 | 27 | 28 | 29 | 其他仓库 30 | others 31 | others 32 | 33 | 34 | -------------------------------------------------------------------------------- /addons/warehouse/goods.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp.osv import osv 4 | from utils import safe_division 5 | 6 | from openerp import models, fields 7 | from openerp import api 8 | 9 | 10 | class goods(models.Model): 11 | _inherit = 'goods' 12 | 13 | default_wh = fields.Many2one('warehouse', u'默认库位') 14 | 15 | # 使用SQL来取得指定产品情况下的库存数量 16 | def get_stock_qty(self): 17 | for goods in self: 18 | self.env.cr.execute(''' 19 | SELECT sum(line.qty_remaining) as qty, 20 | sum(line.qty_remaining * (line.subtotal / line.goods_qty)) as subtotal, 21 | wh.name as warehouse 22 | FROM wh_move_line line 23 | LEFT JOIN warehouse wh ON line.warehouse_dest_id = wh.id 24 | 25 | WHERE line.qty_remaining > 0 26 | AND wh.type = 'stock' 27 | AND line.state = 'done' 28 | AND line.goods_id = %s 29 | 30 | GROUP BY wh.name 31 | ''' % (goods.id, )) 32 | 33 | return self.env.cr.dictfetchall() 34 | 35 | def get_cost(self): 36 | # TODO 产品上需要一个字段来记录成本 37 | return 1 38 | 39 | def get_suggested_cost_by_warehouse(self, warehouse, qty): 40 | records, subtotal = self.get_matching_records(warehouse, qty, ignore_stock=True) 41 | 42 | matching_qty = sum(record.get('qty') for record in records) 43 | if matching_qty: 44 | cost = safe_division(subtotal, matching_qty) 45 | if matching_qty >= qty: 46 | return subtotal, cost 47 | else: 48 | cost = self.get_cost() 49 | return cost * qty, cost 50 | 51 | def is_using_matching(self): 52 | return True 53 | 54 | def is_using_batch(self): 55 | for goods in self: 56 | return goods.using_batch 57 | 58 | return False 59 | 60 | def get_matching_records(self, warehouse, qty, ignore_stock=False): 61 | matching_records = [] 62 | for goods in self: 63 | domain = [ 64 | ('qty_remaining', '>', 0), 65 | ('state', '=', 'done'), 66 | ('warehouse_dest_id', '=', warehouse.id), 67 | ('goods_id', '=', goods.id) 68 | ] 69 | 70 | # TODO @zzx需要在大量数据的情况下评估一下速度 71 | lines = self.env['wh.move.line'].search(domain, order='date, id') 72 | 73 | qty_to_go, subtotal = qty, 0 74 | for line in lines: 75 | if qty_to_go <= 0: 76 | break 77 | 78 | matching_qty = min(line.qty_remaining, qty_to_go) 79 | matching_records.append({'line_in_id': line.id, 'qty': matching_qty}) 80 | subtotal += matching_qty * line.get_real_price() 81 | 82 | qty_to_go -= matching_qty 83 | else: 84 | if not ignore_stock and qty_to_go > 0: 85 | raise osv.except_osv(u'错误', u'产品%s的库存数量不够本次出库行为' % (goods.name, )) 86 | 87 | return matching_records, subtotal 88 | -------------------------------------------------------------------------------- /addons/warehouse/html/move_lot.html: -------------------------------------------------------------------------------- 1 | {% for lot in lots %} 2 | {{lot.name}}--{{lot.goods_qty}} 3 | {% endfor %} -------------------------------------------------------------------------------- /addons/warehouse/menu/warehouse_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /addons/warehouse/move_matching.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp.osv import osv 4 | import openerp.addons.decimal_precision as dp 5 | from utils import safe_division 6 | from openerp import models, fields, api 7 | 8 | 9 | class wh_move_matching(models.Model): 10 | _name = 'wh.move.matching' 11 | 12 | line_in_id = fields.Many2one('wh.move.line', u'出库', ondelete='set null', required=True, index=True) 13 | line_out_id = fields.Many2one('wh.move.line', u'入库', ondelete='set null', required=True, index=True) 14 | qty = fields.Float(u'数量', digits_compute=dp.get_precision('Goods Quantity'), required=True) 15 | 16 | def create_matching(self, line_in_id, line_out_id, qty): 17 | res = { 18 | 'line_out_id': line_out_id, 19 | 'line_in_id': line_in_id, 20 | 'qty': qty, 21 | } 22 | 23 | return self.create(res) 24 | 25 | 26 | class wh_move_line(models.Model): 27 | _inherit = 'wh.move.line' 28 | 29 | qty_remaining = fields.Float(compute='_get_qty_remaining', string=u'剩余数量', 30 | digits_compute=dp.get_precision('Goods Quantity'), index=True, store=True,readonly=True) 31 | 32 | matching_in_ids = fields.One2many('wh.move.matching', 'line_in_id', string=u'关联的入库') 33 | matching_out_ids = fields.One2many('wh.move.matching', 'line_out_id', string=u'关联的出库') 34 | 35 | @api.multi 36 | def copy(self): 37 | # TODO 奇怪,返回值似乎被wrapper了 38 | res = super(wh_move_line, self).copy() 39 | 40 | if res.get('warehouse_id') and res.get('warehouse_dest_id') and res.get('goods_id'): 41 | warehouses = self.env['warehouse'].browse([res.get('warehouse_id'), 42 | res.get('warehouse_dest_id')]) 43 | 44 | if warehouses[0].type == 'stock' and warehouses[1].type != 'stock': 45 | goods = self.env['goods'].browse(res.get('goods_id')) 46 | subtotal, price = goods.get_suggested_cost_by_warehouse(warehouses[0], res.get('goods_qty')) 47 | res.update({'price': price, 'subtotal': subtotal}) 48 | 49 | return res 50 | 51 | # 这样的function字段的使用方式需要验证一下 52 | @api.one 53 | @api.depends('goods_qty', 'matching_in_ids.qty') 54 | def _get_qty_remaining(self): 55 | self.qty_remaining = self.goods_qty - sum(match.qty for match in self.matching_in_ids) 56 | 57 | def get_matching_records_by_lot(self): 58 | for line in self: 59 | if line.goods_qty > line.lot_id.qty_remaining: 60 | raise osv.except_osv(u'错误', u'产品%s的库存数量不够本次出库行为' % (self.goods_id.name, )) 61 | 62 | return [{'line_in_id': line.lot_id.id, 'qty': line.goods_qty}], \ 63 | line.lot_id.price * line.goods_qty 64 | 65 | return [] 66 | 67 | def prev_action_done(self): 68 | matching_obj = self.env['wh.move.matching'] 69 | for line in self: 70 | if line.warehouse_id.type == 'stock' and line.goods_id.is_using_matching(): 71 | if line.goods_id.is_using_batch(): 72 | matching_records, subtotal = line.get_matching_records_by_lot() 73 | for matching in matching_records: 74 | matching_obj.create_matching(matching.get('line_in_id'), 75 | line.id, matching.get('qty')) 76 | else: 77 | matching_records, subtotal = line.goods_id.get_matching_records( 78 | line.warehouse_id, line.goods_qty) 79 | 80 | for matching in matching_records: 81 | matching_obj.create_matching(matching.get('line_in_id'), 82 | line.id, matching.get('qty')) 83 | 84 | line.price = safe_division(subtotal, line.goods_qty) 85 | line.subtotal = subtotal 86 | 87 | return super(wh_move_line, self).prev_action_done() 88 | 89 | def prev_action_cancel(self): 90 | for line in self: 91 | if line.qty_remaining != line.goods_qty: 92 | raise osv.except_osv(u'错误', u'当前的入库已经被其他出库匹配,请先取消相关的出库') 93 | 94 | line.matching_in_ids.unlink() 95 | line.matching_out_ids.unlink() 96 | 97 | return super(wh_move_line, self).prev_action_cancel() 98 | -------------------------------------------------------------------------------- /addons/warehouse/report/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import report_base 3 | import stock_balance 4 | import stock_transceive 5 | import lot_status 6 | import lot_track 7 | import stock_transceive_collect 8 | -------------------------------------------------------------------------------- /addons/warehouse/report/lot_status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp import tools 4 | import openerp.addons.decimal_precision as dp 5 | from openerp import models, fields 6 | 7 | 8 | class report_lot_status(models.Model): 9 | _name = 'report.lot.status' 10 | _auto = False 11 | 12 | goods = fields.Char(u'产品') 13 | uom = fields.Char(u'单位') 14 | lot = fields.Char(u'序列号') 15 | status = fields.Char(u'状态') 16 | warehouse = fields.Char(u'仓库') 17 | date = fields.Date(u'日期') 18 | qty = fields.Float(u'数量', digits_compute=dp.get_precision('Goods Quantity')) 19 | 20 | def init(self, cr): 21 | tools.drop_view_if_exists(cr, 'report_lot_status') 22 | cr.execute( 23 | """ 24 | create or replace view report_lot_status as ( 25 | SELECT MIN(line.id) as id, 26 | goods.name as goods, 27 | uom.name as uom, 28 | line.lot as lot, 29 | '在库' as status, 30 | wh.name as warehouse, 31 | max(line.date) as date, 32 | sum(line.qty_remaining) as qty 33 | 34 | FROM wh_move_line line 35 | LEFT JOIN goods goods ON line.goods_id = goods.id 36 | LEFT JOIN uom uom ON line.uom_id = uom.id 37 | LEFT JOIN warehouse wh ON line.warehouse_dest_id = wh.id 38 | 39 | WHERE line.lot IS NOT NULL 40 | AND line.qty_remaining > 0 41 | AND wh.type = 'stock' 42 | 43 | GROUP BY goods, uom, lot, warehouse 44 | 45 | ORDER BY goods, lot, warehouse 46 | ) 47 | """) 48 | -------------------------------------------------------------------------------- /addons/warehouse/report/lot_status_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | report.lot.status.tree 6 | report.lot.status 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | report.lot.status.search 22 | report.lot.status 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | report.lot.status.graph 40 | report.lot.status 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /addons/warehouse/report/lot_track.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import openerp.addons.decimal_precision as dp 4 | from openerp import models, fields 5 | 6 | 7 | class report_lot_track(models.Model): 8 | _name = 'report.lot.track' 9 | _inherit = 'report.base' 10 | 11 | goods = fields.Char(u'产品') 12 | uom = fields.Char(u'单位') 13 | lot = fields.Char(u'序列号') 14 | warehouse = fields.Char(u'仓库') 15 | date = fields.Date(u'日期') 16 | qty = fields.Float(u'数量', digits_compute=dp.get_precision('Goods Quantity')) 17 | origin = fields.Char(u'业务类型') 18 | 19 | def compute_origin(self, results): 20 | line_obj = self.env['wh.move.line'] 21 | for result in results: 22 | line = line_obj.browse(result.get('id')) 23 | result.update({ 24 | 'origin': line.with_context(internal_out=result.get('type') == 'out').get_origin_explain() 25 | }) 26 | 27 | def select_sql(self, sql_type='out'): 28 | return ''' 29 | SELECT line.id as id, 30 | goods.name as goods, 31 | uom.name as uom, 32 | %s.lot as lot, 33 | '%s' as type, 34 | wh.name as warehouse, 35 | line.date as date, 36 | line.qty_remaining as qty 37 | ''' % (sql_type == 'out' and 'lot' or 'line', sql_type) 38 | 39 | def from_sql(self, sql_type='out'): 40 | if sql_type == 'out': 41 | warehouse = 'warehouse_id' 42 | extra = 'LEFT JOIN wh_move_line lot ON line.lot_id = lot.id' 43 | else: 44 | warehouse, extra = 'warehouse_dest_id', '' 45 | 46 | return ''' 47 | FROM wh_move_line line 48 | LEFT JOIN goods goods ON line.goods_id = goods.id 49 | LEFT JOIN uom uom ON line.uom_id = uom.id 50 | LEFT JOIN warehouse wh ON line.{warehouse} = wh.id 51 | {extra} 52 | '''.format(warehouse=warehouse, extra=extra) 53 | 54 | def where_sql(self, sql_type='out'): 55 | return ''' 56 | WHERE line.state = 'done' 57 | AND line.%s IS NOT NULL 58 | AND wh.type = 'stock' 59 | AND line.date >= '{date_start}' 60 | AND line.date < '{date_end}' 61 | AND wh.name ilike '%%{warehouse}%%' 62 | AND goods.name ilike '%%{goods}%%' 63 | ''' % (sql_type == 'out' and 'lot_id' or 'lot') 64 | 65 | def order_sql(self, sql_type='out'): 66 | return ''' 67 | ORDER BY lot, date, goods, warehouse 68 | ''' 69 | 70 | def get_context(self, sql_type='out', context=None): 71 | return { 72 | 'date_start': context.get('date_start') or '', 73 | 'date_end': context.get('date_end') or '', 74 | 'warehouse': context.get('warehouse') or '', 75 | 'goods': context.get('goods') or '', 76 | } 77 | 78 | def _compute_order(self, result, order): 79 | order = order or 'goods DESC' 80 | return super(report_lot_track, self)._compute_order(result, order) 81 | 82 | def collect_data_by_sql(self, sql_type='out'): 83 | out_collection = self.execute_sql(sql_type='out') 84 | in_collection = self.execute_sql(sql_type='in') 85 | 86 | result = out_collection + in_collection 87 | self.compute_origin(result) 88 | 89 | return result 90 | -------------------------------------------------------------------------------- /addons/warehouse/report/lot_track_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | report.lot.track.tree 6 | report.lot.track 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | report.lot.track.search 22 | report.lot.track 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | report.lot.track.graph 40 | report.lot.track 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /addons/warehouse/report/report_base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp.osv import osv 4 | import itertools 5 | import operator 6 | from openerp import models, api 7 | 8 | 9 | class report_base(models.Model): 10 | _name = 'report.base' 11 | _description = u'使用search_read来直接生成数据的基本类,其他类可以直接异名继承当前类来重用搜索、过滤、分组等函数' 12 | 13 | def select_sql(self, sql_type='out'): 14 | return '' 15 | 16 | def from_sql(self, sql_type='out'): 17 | return '' 18 | 19 | def where_sql(self, sql_type='out'): 20 | return '' 21 | 22 | def group_sql(self, sql_type='out'): 23 | return '' 24 | 25 | def order_sql(self, sql_type='out'): 26 | return '' 27 | 28 | def get_context(self, sql_type='out', context=None): 29 | return {} 30 | 31 | @api.model 32 | def execute_sql(self, sql_type='out'): 33 | self.env.cr.execute((self.select_sql(sql_type) + self.from_sql(sql_type) + self.where_sql( 34 | sql_type) + self.group_sql(sql_type) + self.order_sql( 35 | sql_type)).format(**self.get_context(sql_type, context=self.env.context))) 36 | 37 | return self.env.cr.dictfetchall() 38 | 39 | def collect_data_by_sql(self, sql_type='out'): 40 | return self.execute_sql(sql_type=sql_type) 41 | 42 | def check_valid_domain(self, domain): 43 | if not isinstance(domain, list): 44 | raise osv.except_osv(u'错误', u'不可识别的domain条件,请检查domain"%s"是否正确' % domain) 45 | 46 | def _get_next_domain(self, domains, index): 47 | domain = domains[index] 48 | if domain == '|': 49 | _, index = self.get_next_or_domain(domains, index + 1) 50 | else: 51 | index += 1 52 | self.check_valid_domain(domain) 53 | 54 | return index 55 | 56 | def get_next_or_domain(self, domains, index): 57 | index = self._get_next_domain(domains, index) 58 | 59 | return index, self._get_next_domain(domains, index) 60 | 61 | def _process_domain(self, result, domain): 62 | if domain and len(domain) == 3: 63 | field, opto, value = domain 64 | 65 | compute_operator = { 66 | 'ilike': lambda field, value: str(value).lower() in str(field).lower(), 67 | 'like': lambda field, value: str(value) in str(field), 68 | 'not ilike': lambda field, value: str(value).lower() not in str(field).lower(), 69 | 'not like': lambda field, value: str(value) not in str(field), 70 | '=': operator.eq, 71 | '!=': operator.ne, 72 | '>': operator.gt, 73 | '<': operator.lt, 74 | '>=': operator.ge, 75 | '<=': operator.le, 76 | } 77 | 78 | opto = opto.lower() 79 | if field in result: 80 | if opto in compute_operator.iterkeys(): 81 | return compute_operator.get(opto)(result.get(field), value) 82 | 83 | raise osv.except_osv(u'错误', u'暂时无法解析的domain条件%s,请联系管理员' % domain) 84 | 85 | raise osv.except_osv(u'错误', u'不可识别的domain条件,请检查domain"%s"是否正确' % domain) 86 | 87 | def _compute_domain_util(self, result, domains): 88 | index = 0 89 | while index < len(domains): 90 | domain = domains[index] 91 | index += 1 92 | if domain == '|': 93 | left_index, right_index = self.get_next_or_domain(domains, index) 94 | 95 | if not self._compute_domain_util(result, domains[index:left_index]) and not self._compute_domain_util(result, domains[left_index:right_index]): 96 | return False 97 | 98 | index = right_index 99 | 100 | else: 101 | self.check_valid_domain(domain) 102 | if not self._process_domain(result, domain): 103 | return False 104 | 105 | return True 106 | 107 | def _compute_domain(self, result, domain): 108 | return filter(lambda res: self._compute_domain_util(res, domain), result) 109 | 110 | @api.model 111 | def read_group(self, domain, fields, groupby, offset=0, limit=80, orderby=False, lazy=True): 112 | 113 | def dict_plus(collect, values): 114 | for key, value in values.iteritems(): 115 | if isinstance(value, (long, int, float)): 116 | if key not in collect: 117 | collect[key] = 0 118 | collect[key] += value 119 | 120 | collect[groupby[0] + '_count'] += 1 121 | 122 | return collect 123 | 124 | res = [] 125 | values = self.search_read(domain=domain, fields=fields, offset=offset, limit=limit or 80, order=orderby) 126 | 127 | if groupby: 128 | key = operator.itemgetter(groupby[0]) 129 | for group, itervalue in itertools.groupby(sorted(values, key=key), key): 130 | collect = {'__domain': [(groupby[0], '=', group)], groupby[0]: group, groupby[0] + '_count': 0} 131 | collect = reduce(lambda collect, value: dict_plus(collect, value), itervalue, collect) 132 | 133 | if len(groupby) > 1: 134 | collect.update({ 135 | '__context': {'group_by': groupby[1:]} 136 | }) 137 | 138 | if domain: 139 | collect['__domain'].extend(domain) 140 | 141 | res.append(collect) 142 | 143 | return res 144 | 145 | def _compute_order(self, result, order): 146 | # TODO 暂时不支持多重排序 147 | if order: 148 | order = order.partition(',')[0].partition(' ') 149 | result.sort(key=lambda item: item.get(order[0]), reverse=order[2] == 'ASC') 150 | 151 | return result 152 | 153 | def _compute_limit_and_offset(self, result, limit, offset): 154 | return result[offset:limit + offset] 155 | 156 | @api.model 157 | def search_read(self, domain=None, fields=None, offset=0, limit=80, order=None): 158 | result = self.collect_data_by_sql(sql_type='out') 159 | 160 | result = self._compute_domain(result, domain) 161 | result = self._compute_order(result, order) 162 | result = self._compute_limit_and_offset(result, limit, offset) 163 | 164 | return result 165 | 166 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 167 | -------------------------------------------------------------------------------- /addons/warehouse/report/stock_balance.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp import tools 4 | import openerp.addons.decimal_precision as dp 5 | from openerp import models, fields 6 | 7 | 8 | class report_stock_balance(models.Model): 9 | _name = 'report.stock.balance' 10 | _auto = False 11 | 12 | goods = fields.Char(u'产品') 13 | uom = fields.Char(u'单位') 14 | lot = fields.Char(u'批次') 15 | warehouse = fields.Char(u'仓库') 16 | goods_qty = fields.Float('数量', digits_compute=dp.get_precision('Goods Quantity')) 17 | cost = fields.Float(u'成本', digits_compute=dp.get_precision('Accounting')) 18 | 19 | def init(self, cr): 20 | tools.drop_view_if_exists(cr, 'report_stock_balance') 21 | cr.execute( 22 | """ 23 | create or replace view report_stock_balance as ( 24 | SELECT min(line.id) as id, 25 | goods.name as goods, 26 | line.lot as lot, 27 | uom.name as uom, 28 | wh.name as warehouse, 29 | sum(line.qty_remaining) as goods_qty, 30 | sum(line.subtotal) as cost 31 | 32 | FROM wh_move_line line 33 | LEFT JOIN warehouse wh ON line.warehouse_dest_id = wh.id 34 | LEFT JOIN goods goods ON line.goods_id = goods.id 35 | LEFT JOIN uom uom ON line.uom_id = uom.id 36 | 37 | WHERE line.qty_remaining > 0 38 | AND wh.type = 'stock' 39 | AND line.state = 'done' 40 | 41 | GROUP BY wh.name, line.lot, goods.name, uom.name 42 | 43 | ORDER BY goods.name, wh.name, goods_qty asc 44 | ) 45 | """) 46 | 47 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 48 | -------------------------------------------------------------------------------- /addons/warehouse/report/stock_balance_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | report.stock.balance.tree 6 | report.stock.balance 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | report.stock.balance.graph 21 | report.stock.balance 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | report.stock.balance.search 35 | report.stock.balance 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /addons/warehouse/report/stock_transceive.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import openerp.addons.decimal_precision as dp 4 | from openerp import models, fields 5 | 6 | 7 | class report_stock_transceive(models.Model): 8 | _name = 'report.stock.transceive' 9 | _inherit = 'report.base' 10 | 11 | goods = fields.Char(u'产品') 12 | uom = fields.Char(u'单位') 13 | warehouse = fields.Char(u'仓库') 14 | goods_qty_begain = fields.Float('期初数量', digits_compute=dp.get_precision('Goods Quantity')) 15 | cost_begain = fields.Float(u'期初成本', digits_compute=dp.get_precision('Accounting')) 16 | goods_qty_end = fields.Float('期末数量', digits_compute=dp.get_precision('Goods Quantity')) 17 | cost_end = fields.Float(u'期末成本', digits_compute=dp.get_precision('Accounting')) 18 | goods_qty_out = fields.Float('出库数量', digits_compute=dp.get_precision('Goods Quantity')) 19 | cost_out = fields.Float(u'出库成本', digits_compute=dp.get_precision('Accounting')) 20 | goods_qty_in = fields.Float('入库数量', digits_compute=dp.get_precision('Goods Quantity')) 21 | cost_in = fields.Float(u'入库成本', digits_compute=dp.get_precision('Accounting')) 22 | 23 | def select_sql(self, sql_type='out'): 24 | return ''' 25 | SELECT min(line.id) as id, 26 | goods.name as goods, 27 | uom.name as uom, 28 | wh.name as warehouse, 29 | sum(case when line.date < '{date_start}' THEN line.goods_qty ELSE 0 END) as goods_qty_begain, 30 | sum(case when line.date < '{date_start}' THEN line.subtotal ELSE 0 END) as cost_begain, 31 | sum(case when line.date < '{date_end}' THEN line.goods_qty ELSE 0 END) as goods_qty_end, 32 | sum(case when line.date < '{date_end}' THEN line.subtotal ELSE 0 END) as cost_end, 33 | sum(case when line.date < '{date_end}' AND line.date >= '{date_start}' THEN line.goods_qty ELSE 0 END) as goods_qty, 34 | sum(case when line.date < '{date_end}' AND line.date >= '{date_start}' THEN line.subtotal ELSE 0 END) as cost 35 | ''' 36 | 37 | def from_sql(self, sql_type='out'): 38 | return ''' 39 | FROM wh_move_line line 40 | LEFT JOIN goods goods ON line.goods_id = goods.id 41 | LEFT JOIN uom uom ON line.uom_id = uom.id 42 | LEFT JOIN warehouse wh ON line.%s = wh.id 43 | ''' % (sql_type == 'out' and 'warehouse_id' or 'warehouse_dest_id') 44 | 45 | def where_sql(self, sql_type='out'): 46 | return ''' 47 | WHERE line.state = 'done' 48 | AND wh.type = 'stock' 49 | AND line.date < '{date_end}' 50 | AND wh.name ilike '%{warehouse}%' 51 | AND goods.name ilike '%{goods}%' 52 | ''' 53 | 54 | def group_sql(self, sql_type='out'): 55 | return ''' 56 | GROUP BY goods.name, uom.name, wh.name 57 | ''' 58 | 59 | def order_sql(self, sql_type='out'): 60 | return ''' 61 | ORDER BY goods.name, wh.name 62 | ''' 63 | 64 | def get_context(self, sql_type='out', context=None): 65 | return { 66 | 'date_start': context.get('date_start') or '', 67 | 'date_end': context.get('date_end') or '', 68 | 'warehouse': context.get('warehouse') or '', 69 | 'goods': context.get('goods') or '', 70 | } 71 | 72 | def get_record_key(self, record, sql_type='out'): 73 | return (record.get('goods'), record.get('uom'), record.get('warehouse')) 74 | 75 | def unzip_record_key(self, key): 76 | return { 77 | 'goods': key[0], 78 | 'uom': key[1], 79 | 'warehouse': key[2], 80 | } 81 | 82 | def get_default_value_by_record(self, record, sql_type='out'): 83 | return { 84 | 'id': record.get('id'), 85 | } 86 | 87 | def update_record_value(self, value, record, sql_type='out'): 88 | tag = sql_type == 'out' and -1 or 1 89 | value.update({ 90 | 'goods_qty_begain': value.get('goods_qty_begain', 0) + tag * record.get('goods_qty_begain', 0), 91 | 'cost_begain': value.get('cost_begain', 0) + tag * record.get('cost_begain', 0), 92 | 'goods_qty_end': value.get('goods_qty_end', 0) + tag * record.get('goods_qty_end', 0), 93 | 'cost_end': value.get('cost_end', 0) + tag * record.get('cost_end', 0), 94 | 95 | 'goods_qty_out': value.get('goods_qty_out', 0) + (sql_type == 'out' and record.get('goods_qty', 0) or 0), 96 | 'cost_out': value.get('cost_out', 0) + (sql_type == 'out' and record.get('cost', 0) or 0), 97 | 'goods_qty_in': value.get('goods_qty_in', 0) + (sql_type == 'in' and record.get('goods_qty', 0) or 0), 98 | 'cost_in': value.get('cost_in', 0) + (sql_type == 'in' and record.get('cost', 0) or 0), 99 | }) 100 | 101 | def compute_history_stock_by_collect(self, res, records, sql_type='out'): 102 | for record in records: 103 | record_key = self.get_record_key(record, sql_type=sql_type) 104 | if not res.get(record_key): 105 | res[record_key] = self.get_default_value_by_record(record, sql_type=sql_type) 106 | 107 | self.update_record_value(res[record_key], record, sql_type=sql_type) 108 | 109 | def collect_data_by_sql(self, sql_type='out'): 110 | out_collection = self.execute_sql(sql_type='out') 111 | in_collection = self.execute_sql(sql_type='in') 112 | 113 | res = {} 114 | self.compute_history_stock_by_collect(res, in_collection, sql_type='in') 115 | self.compute_history_stock_by_collect(res, out_collection, sql_type='out') 116 | 117 | result = [] 118 | for key, value in res.iteritems(): 119 | value.update(self.unzip_record_key(key)) 120 | result.append(value) 121 | 122 | return result 123 | 124 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 125 | -------------------------------------------------------------------------------- /addons/warehouse/report/stock_transceive_collect.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import openerp.addons.decimal_precision as dp 4 | from openerp import models, fields 5 | 6 | 7 | class report_stock_transceive_collect(models.Model): 8 | _name = 'report.stock.transceive.collect' 9 | _inherit = 'report.stock.transceive' 10 | 11 | ORIGIN_MAP = { 12 | ('wh.internal', True): 'internal_out', 13 | ('wh.internal', False): 'internal_in', 14 | 15 | 'wh.out.others': 'others_out', 16 | 'wh.in.others': 'others_in', 17 | 'wh.out.losses': 'losses_out', 18 | 'wh.in.overage': 'overage_in', 19 | 20 | ('wh.assembly', True): 'assembly_out', 21 | ('wh.assembly', False): 'assembly_in', 22 | 23 | ('wh.disassembly', True): 'disassembly_out', 24 | ('wh.disassembly', False): 'disassembly_in', 25 | } 26 | 27 | NEED_TUPLE_ORIGIN_MAP = ['wh.internal', 'wh.assembly', 'wh.disassembly'] 28 | 29 | internal_in_qty = fields.Float(u'调拨入库数量', digits_compute=dp.get_precision('Goods Quantity')) 30 | internal_in_cost = fields.Float(u'调拨入库成本', digits_compute=dp.get_precision('Accounting')) 31 | 32 | purchase_in_qty = fields.Float(u'普通采购数量', digits_compute=dp.get_precision('Goods Quantity')) 33 | purchase_in_cost = fields.Float(u'普通采购成本', digits_compute=dp.get_precision('Accounting')) 34 | 35 | sale_in_qty = fields.Float(u'销售退回数量', digits_compute=dp.get_precision('Goods Quantity')) 36 | sale_in_cost = fields.Float(u'销售退回成本', digits_compute=dp.get_precision('Accounting')) 37 | 38 | others_in_qty = fields.Float(u'其他入库数量', digits_compute=dp.get_precision('Goods Quantity')) 39 | others_in_cost = fields.Float(u'其他入库成本', digits_compute=dp.get_precision('Accounting')) 40 | 41 | overage_in_qty = fields.Float(u'盘盈数量', digits_compute=dp.get_precision('Goods Quantity')) 42 | overage_in_cost = fields.Float(u'盘盈成本', digits_compute=dp.get_precision('Accounting')) 43 | 44 | assembly_in_qty = fields.Float(u'组装单入库数量', digits_compute=dp.get_precision('Goods Quantity')) 45 | assembly_in_cost = fields.Float(u'组装单入库成本', digits_compute=dp.get_precision('Accounting')) 46 | 47 | disassembly_in_qty = fields.Float(u'拆卸单入库数量', digits_compute=dp.get_precision('Goods Quantity')) 48 | disassembly_in_cost = fields.Float(u'拆卸单入库成本', digits_compute=dp.get_precision('Accounting')) 49 | 50 | internal_out_qty = fields.Float(u'调拨出库数量', digits_compute=dp.get_precision('Goods Quantity')) 51 | internal_out_cost = fields.Float(u'调拨出库成本', digits_compute=dp.get_precision('Accounting')) 52 | 53 | purchase_out_qty = fields.Float(u'采购退回数量', digits_compute=dp.get_precision('Goods Quantity')) 54 | purchase_out_cost = fields.Float(u'采购退回成本', digits_compute=dp.get_precision('Accounting')) 55 | 56 | sale_out_qty = fields.Float(u'普通销售购量', digits_compute=dp.get_precision('Goods Quantity')) 57 | sale_out_cost = fields.Float(u'普通销售购本', digits_compute=dp.get_precision('Accounting')) 58 | 59 | others_out_qty = fields.Float(u'其他出库数量', digits_compute=dp.get_precision('Goods Quantity')) 60 | others_out_cost = fields.Float(u'其他出库成本', digits_compute=dp.get_precision('Accounting')) 61 | 62 | losses_out_qty = fields.Float(u'盘亏数量', digits_compute=dp.get_precision('Goods Quantity')) 63 | losses_out_cost = fields.Float(u'盘亏成本', digits_compute=dp.get_precision('Accounting')) 64 | 65 | assembly_out_qty = fields.Float(u'组装单出库数量', digits_compute=dp.get_precision('Goods Quantity')) 66 | assembly_out_cost = fields.Float(u'组装单出库成本', digits_compute=dp.get_precision('Accounting')) 67 | 68 | disassembly_out_qty = fields.Float(u'拆卸单出库数量', digits_compute=dp.get_precision('Goods Quantity')) 69 | disassembly_out_cost = fields.Float(u'拆卸单出库成本', digits_compute=dp.get_precision('Accounting')) 70 | 71 | def compute_specific_data(self, res): 72 | line_obj = self.env['wh.move.line'] 73 | for result in res: 74 | for line in line_obj.browse(result.get('lines')): 75 | if line.move_id.origin in self.NEED_TUPLE_ORIGIN_MAP: 76 | origin = self.ORIGIN_MAP.get((line.move_id.origin, result.get('warehouse') == line.warehouse_id.name)) 77 | else: 78 | origin = self.ORIGIN_MAP.get(line.move_id.origin) 79 | 80 | if not result.get(origin + '_qty'): 81 | result[origin + '_qty'] = 0 82 | result[origin + '_cost'] = 0 83 | 84 | result[origin + '_qty'] += line.goods_qty 85 | result[origin + '_cost'] += line.subtotal 86 | 87 | def select_sql(self, sql_type='out'): 88 | select = super(report_stock_transceive_collect, self).select_sql(sql_type=sql_type) 89 | select += ', array_agg(line.id) as lines' 90 | 91 | return select 92 | 93 | def collect_data_by_sql(self, sql_type='out'): 94 | res = super(report_stock_transceive_collect, self).collect_data_by_sql(sql_type=sql_type) 95 | self.compute_specific_data(res) 96 | 97 | return res 98 | 99 | def update_record_value(self, value, record, sql_type='out'): 100 | super(report_stock_transceive_collect, self).update_record_value(value, record, sql_type=sql_type) 101 | value.update({ 102 | 'lines': value.get('lines', []) + record.get('lines', ''), 103 | }) 104 | -------------------------------------------------------------------------------- /addons/warehouse/report/stock_transceive_collect_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | report.stock.transceive.collect.tree 6 | report.stock.transceive.collect 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | report.stock.transceive.collect.search 60 | report.stock.transceive.collect 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /addons/warehouse/report/stock_transceive_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | report.stock.transceive.tree 6 | report.stock.transceive 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | report.stock.transceive.search 28 | report.stock.transceive 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | report.stock.transceive.graph 46 | report.stock.transceive 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /addons/warehouse/static/src/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | span.selection-clickable-mode, span.boolean-clickable-mode { 3 | background-color: #7AFF00; 4 | color: white; 5 | padding: 4px; 6 | } 7 | 8 | span.selection-clickable-mode[data-value=disable], span.boolean-clickable-mode[data-value=false]{ 9 | background-color: #ADADAD; 10 | } 11 | 12 | .openerp .oe_form_editable 13 | .oe_form .oe_form_field_date:not(.oe_inline) input { 14 | width: 200px; 15 | } -------------------------------------------------------------------------------- /addons/warehouse/static/src/js/warehouse_widget.js: -------------------------------------------------------------------------------- 1 | openerp.warehouse = function(instance) { 2 | instance.web.list.Column.include({ 3 | _format: function(row_data, options) { 4 | if (this.widget === 'selection_clickable' && !_.isUndefined(this.selection)) { 5 | var field_name = row_data[this.id]['value'], 6 | select = _.find(this.selection, function(select) { return select[0] === field_name}) 7 | 8 | if (_.isUndefined(select)) { 9 | delete this.widget; 10 | return this._super(row_data, options) 11 | } 12 | return _.str.sprintf("%s", 13 | select[0], select[1]); 14 | } else if (this.widget === 'boolean_clickable') { 15 | console.warn('boolean_clickable', row_data, this); 16 | 17 | return _.str.sprintf("%s", 18 | row_data[this.id]['value'], 19 | row_data[this.id]['value']? '已启用' : '已禁止'); 20 | 21 | } else { 22 | return this._super(row_data, options); 23 | }; 24 | 25 | }, 26 | }); 27 | 28 | instance.web.ListView.List.include({ 29 | render: function() { 30 | var self = this; 31 | result = this._super(this, arguments), 32 | 33 | this.$current.delegate('span.selection-clickable-mode', 'click', function(e) { 34 | e.stopPropagation(); 35 | var current = $(this), 36 | notify = instance.web.notification, 37 | data_id = current.closest('tr').data('id'), 38 | field_name = current.closest('td').data('field'), 39 | field_value = current.data('value'), 40 | model = new instance.web.Model(self.dataset.model); 41 | 42 | var column = _.find(self.columns, function(column) { return column.id === field_name}) 43 | if (_.isUndefined(column)) { 44 | notify.notify('抱歉', '当前列表中没有定义当前字段'); 45 | return; 46 | } 47 | 48 | var options = instance.web.py_eval(column.options || '{}'); 49 | if (_.isUndefined(options.selection)) { 50 | notify.notify('错误', '需要在字段的options中定义selection属性') 51 | return; 52 | } 53 | 54 | var index = _.indexOf(options.selection, field_value); 55 | if (index === -1) { 56 | notify.notify('错误', '当前字段的值没有在options中selection属性定义') 57 | return; 58 | } 59 | index += 1; 60 | if (index === options.selection.length) { 61 | index = 0; 62 | }; 63 | 64 | var next_value = options.selection[index]; 65 | 66 | res = {} 67 | res[field_name] = next_value; 68 | model.call('write', [parseInt(data_id), res]).then(function(result) { 69 | current.data('value', next_value); 70 | current.attr('data-value', next_value); 71 | var select = _.find(column.selection, function(select) { return select[0] === next_value}) 72 | current.text(select[1]); 73 | }) 74 | }).delegate('span.boolean-clickable-mode', 'click', function(e) { 75 | e.stopPropagation(); 76 | var current = $(this), 77 | notify = instance.web.notification, 78 | data_id = current.closest('tr').data('id'), 79 | field_name = current.closest('td').data('field'), 80 | field_value = !current.data('value'), 81 | field_text = field_value? '已启用' : '已禁止', 82 | model = new instance.web.Model(self.dataset.model); 83 | 84 | var column = _.find(self.columns, function(column) { return column.id === field_name}) 85 | if (_.isUndefined(column)) { 86 | notify.notify('抱歉', '当前列表中没有定义当前字段'); 87 | return; 88 | } 89 | 90 | res = {} 91 | res[field_name] = field_value; 92 | model.call('write', [parseInt(data_id), res]).then(function(result) { 93 | current.data('value', field_value); 94 | current.attr('data-value', field_value); 95 | current.text(field_text); 96 | }); 97 | }); 98 | return result; 99 | }, 100 | }); 101 | 102 | instance.warehouse.selectionClickable = openerp.web.list.Column.extend({}); 103 | instance.web.list.columns.add('field.selection_clickable', 'instance.warehouse.selectionClickable'); 104 | 105 | instance.warehouse.booleanClickable = openerp.web.list.Column.extend({}); 106 | instance.web.list.columns.add('field.boolean_clickable', 'instance.warehouse.booleanClickable'); 107 | }; -------------------------------------------------------------------------------- /addons/warehouse/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import functools 3 | 4 | 5 | def safe_division(divisor, dividend): 6 | return dividend != 0 and divisor / dividend or 0 7 | 8 | 9 | def create_name(method): 10 | @functools.wraps(method) 11 | def func(self, vals): 12 | if vals.get('name', '/') == '/': 13 | vals.update({'name': self.env['ir.sequence'].get(self._name) or '/'}) 14 | 15 | return method(self, vals) 16 | 17 | return func 18 | 19 | 20 | def create_origin(method): 21 | @functools.wraps(method) 22 | def func(self, vals): 23 | if hasattr(self, 'get_move_origin'): 24 | vals.update({'origin': self.get_move_origin(vals)}) 25 | else: 26 | vals.update({'origin': self._name}) 27 | 28 | return method(self, vals) 29 | 30 | return func 31 | 32 | 33 | def inherits_after(res_back=True): 34 | def wrapper(method): 35 | @functools.wraps(method) 36 | def func(self, *args, **kwargs): 37 | 38 | res_before = execute_inherits_func(self, method.func_name, args, kwargs) 39 | res_after = method(self, *args, **kwargs) 40 | 41 | if res_back: 42 | return res_after 43 | else: 44 | return res_before 45 | 46 | return func 47 | return wrapper 48 | 49 | 50 | def inherits(res_back=True): 51 | def wrapper(method): 52 | @functools.wraps(method) 53 | def func(self, *args, **kwargs): 54 | 55 | res_after = method(self, *args, **kwargs) 56 | res_before = execute_inherits_func(self, method.func_name, args, kwargs) 57 | 58 | if res_back: 59 | return res_after 60 | else: 61 | return res_before 62 | 63 | return func 64 | return wrapper 65 | 66 | 67 | def execute_inherits_func(self, method_name, args, kwargs): 68 | if self._inherits and len(self._inherits) != 1: 69 | raise ValueError(u'错误,当前对象不存在多重继承,或者存在多个多重继承') 70 | 71 | model, field = self._inherits.items()[0] 72 | values = self.read([field]) 73 | field_ids = map(lambda value: value[field][0], values) 74 | 75 | models = self.env[model].browse(field_ids) 76 | return getattr(models, method_name)(*args, **kwargs) 77 | -------------------------------------------------------------------------------- /addons/warehouse/view/assets_backend.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /addons/warehouse/view/goods_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | goods.form.inherit 6 | goods 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /addons/warehouse/view/inventory_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | wh.inventory.tree 6 | wh.inventory 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | wh.inventory.form 21 | wh.inventory 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 在盘点完成前,请不要做出入库单据,盘点库存与查询时的系统库存比较计算盈亏,如果库存在查询后又发生了变化,需要重新查询一次获取最新的库存信息 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /addons/warehouse/warehouse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp.osv import osv 4 | from openerp import models 5 | from openerp import fields 6 | from openerp import api 7 | 8 | 9 | class warehouse(models.Model): 10 | _inherit = 'warehouse' 11 | 12 | WAREHOUSE_TYPE = [ 13 | ('stock', u'库存'), 14 | ('supplier', u'供应商'), 15 | ('customer', u'客户'), 16 | ('inventory', u'盘点'), 17 | ('production', u'生产'), 18 | ('others', u'其他'), 19 | ] 20 | 21 | name = fields.Char(u'仓库名称') 22 | code = fields.Char(u'仓库编号') 23 | type = fields.Selection(WAREHOUSE_TYPE, '类型', default='stock') 24 | active = fields.Boolean(u'有效', default=True) 25 | 26 | # 使用SQL来取得指定仓库情况下的库存数量 27 | def get_stock_qty(self): 28 | for warehouse in self: 29 | self.env.cr.execute(''' 30 | SELECT sum(line.qty_remaining) as qty, 31 | sum(line.qty_remaining * (line.subtotal / line.goods_qty)) as subtotal, 32 | goods.name as goods 33 | FROM wh_move_line line 34 | LEFT JOIN warehouse wh ON line.warehouse_dest_id = wh.id 35 | LEFT JOIN goods goods ON line.goods_id = goods.id 36 | 37 | WHERE line.qty_remaining > 0 38 | AND wh.type = 'stock' 39 | AND line.state = 'done' 40 | AND line.warehouse_dest_id = %s 41 | 42 | GROUP BY wh.name, goods.name 43 | ''' % (warehouse.id, )) 44 | 45 | return self.env.cr.dictfetchall() 46 | 47 | @api.model 48 | def name_search(self, name='', args=None, operator='ilike', limit=100): 49 | args = args or [] 50 | if not filter(lambda _type: _type[0] == 'type', args): 51 | args = [['type', '=', 'stock']] + args 52 | 53 | return super(warehouse, self).name_search(name=name, args=args, 54 | operator=operator, limit=limit) 55 | 56 | def get_warehouse_by_type(self, _type): 57 | if not _type or _type not in map(lambda _type: _type[0], self.WAREHOUSE_TYPE): 58 | raise ValueError(u'错误,仓库类型"%s"不在预先定义的type之中,请联系管理员' % _type) 59 | 60 | warehouses = self.search([('type', '=', _type)], limit=1, order='id asc') 61 | if not warehouses: 62 | raise osv.except_osv(u'错误', u'不存在该类型"%s"的仓库,请检查基础数据是否全部导入') 63 | 64 | return warehouses[0] 65 | -------------------------------------------------------------------------------- /addons/warehouse/warehouse_move.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp.osv import osv 4 | from openerp import models, fields, api 5 | 6 | 7 | class wh_move(models.Model): 8 | _name = 'wh.move' 9 | 10 | MOVE_STATE = [ 11 | ('draft', u'草稿'), 12 | ('done', u'已审核'), 13 | ] 14 | 15 | origin = fields.Char(u'源单类型', required=True) 16 | name = fields.Char(u'单据编号', copy=False, default='/') 17 | state = fields.Selection(MOVE_STATE, u'状态', copy=False, default='draft') 18 | partner_id = fields.Many2one('partner', u'业务伙伴') 19 | date = fields.Date(u'单据日期', copy=False, default=fields.Date.context_today) 20 | approve_uid = fields.Many2one('res.users', u'审核人', copy=False) 21 | approve_date = fields.Datetime(u'审核日期', copy=False) 22 | line_out_ids = fields.One2many('wh.move.line', 'move_id', u'明细', domain=[('type', '=', 'out')], context={'type': 'out'}, copy=True) 23 | line_in_ids = fields.One2many('wh.move.line', 'move_id', u'明细', domain=[('type', '=', 'in')], context={'type': 'in'}, copy=True) 24 | note = fields.Text(u'备注') 25 | 26 | @api.multi 27 | def unlink(self): 28 | for move in self: 29 | if move.state == 'done': 30 | raise osv.except_osv(u'错误', u'不可以删除已经完成的单据') 31 | 32 | return super(wh_move, self).unlink() 33 | 34 | def prev_approve_order(self): 35 | for order in self: 36 | if not order.line_out_ids and not order.line_in_ids: 37 | raise osv.except_osv(u'错误', u'单据的明细行不可以为空') 38 | 39 | @api.multi 40 | def approve_order(self): 41 | for order in self: 42 | order.prev_approve_order() 43 | order.line_out_ids.action_done() 44 | order.line_in_ids.action_done() 45 | 46 | return self.write({ 47 | 'approve_uid': self.env.uid, 48 | 'approve_date': fields.Datetime.now(self), 49 | 'state': 'done', 50 | }) 51 | 52 | def prev_cancel_approved_order(self): 53 | pass 54 | 55 | @api.multi 56 | def cancel_approved_order(self): 57 | for order in self: 58 | order.prev_cancel_approved_order() 59 | order.line_out_ids.action_cancel() 60 | order.line_in_ids.action_cancel() 61 | 62 | return self.write({ 63 | 'approve_uid': False, 64 | 'approve_date': False, 65 | 'state': 'draft', 66 | }) 67 | -------------------------------------------------------------------------------- /addons/warehouse/warehouse_order.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp.osv import osv 4 | from utils import inherits, inherits_after, create_name, create_origin 5 | import openerp.addons.decimal_precision as dp 6 | from openerp import models, fields, api 7 | 8 | 9 | class wh_out(models.Model): 10 | _name = 'wh.out' 11 | _order = 'date DESC, id DESC' 12 | 13 | _inherits = { 14 | 'wh.move': 'move_id', 15 | } 16 | 17 | TYPE_SELECTION = [ 18 | ('losses', u'盘亏'), 19 | ('others', u'其他出库'), 20 | ] 21 | 22 | move_id = fields.Many2one('wh.move', u'移库单', required=True, index=True, ondelete='cascade') 23 | type = fields.Selection(TYPE_SELECTION, u'业务类别', default='others') 24 | amount_total = fields.Float(compute='_get_amount_total', string=u'合计金额', 25 | store=True,readonly=True, digits_compute=dp.get_precision('Accounting')) 26 | 27 | @api.multi 28 | @inherits() 29 | def approve_order(self): 30 | return True 31 | 32 | @api.multi 33 | @inherits() 34 | def cancel_approved_order(self): 35 | return True 36 | 37 | @api.multi 38 | @inherits_after() 39 | def unlink(self): 40 | return super(wh_out, self).unlink() 41 | 42 | @api.one 43 | @api.depends('line_out_ids.subtotal') 44 | def _get_amount_total(self): 45 | self.amount_total = sum(line.subtotal for line in self.line_out_ids) 46 | 47 | def get_move_origin(self, vals): 48 | return self._name + '.' + vals.get('type') 49 | 50 | @api.model 51 | @create_name 52 | @create_origin 53 | def create(self, vals): 54 | return super(wh_out, self).create(vals) 55 | 56 | 57 | class wh_in(models.Model): 58 | _name = 'wh.in' 59 | _order = 'date DESC, id DESC' 60 | 61 | _inherits = { 62 | 'wh.move': 'move_id', 63 | } 64 | 65 | TYPE_SELECTION = [ 66 | ('overage', u'盘盈'), 67 | ('others', u'其他入库'), 68 | ] 69 | 70 | move_id = fields.Many2one('wh.move', u'移库单', required=True, index=True, ondelete='cascade') 71 | type = fields.Selection(TYPE_SELECTION, u'业务类别', default='others') 72 | amount_total = fields.Float(compute='_get_amount_total', string=u'合计金额', 73 | store=True,readonly=True, digits_compute=dp.get_precision('Accounting')) 74 | 75 | @api.multi 76 | @inherits() 77 | def approve_order(self): 78 | return True 79 | 80 | @api.multi 81 | @inherits() 82 | def cancel_approved_order(self): 83 | return True 84 | 85 | @api.multi 86 | @inherits_after() 87 | def unlink(self): 88 | return super(wh_in, self).unlink() 89 | 90 | @api.one 91 | @api.depends('line_in_ids.subtotal') 92 | def _get_amount_total(self): 93 | self.amount_total = sum(line.subtotal for line in self.line_in_ids) 94 | 95 | def get_move_origin(self, vals): 96 | return self._name + '.' + vals.get('type') 97 | 98 | @api.model 99 | @create_name 100 | @create_origin 101 | def create(self, vals): 102 | return super(wh_in, self).create(vals) 103 | 104 | 105 | class wh_internal(osv.osv): 106 | _name = 'wh.internal' 107 | _order = 'date DESC, id DESC' 108 | 109 | _inherits = { 110 | 'wh.move': 'move_id', 111 | } 112 | 113 | move_id = fields.Many2one('wh.move', u'移库单', required=True, index=True, ondelete='cascade') 114 | amount_total = fields.Float(compute='_get_amount_total', string=u'合计金额', 115 | store=True,readonly=True, digits_compute=dp.get_precision('Accounting')) 116 | 117 | @api.multi 118 | @inherits() 119 | def approve_order(self): 120 | return True 121 | 122 | @api.multi 123 | @inherits() 124 | def cancel_approved_order(self): 125 | return True 126 | 127 | @api.multi 128 | @inherits_after() 129 | def unlink(self): 130 | return super(wh_internal, self).unlink() 131 | 132 | @api.one 133 | @api.depends('line_out_ids.subtotal') 134 | def _get_amount_total(self): 135 | self.amount_total = sum(line.subtotal for line in self.line_out_ids) 136 | 137 | @api.model 138 | @create_name 139 | @create_origin 140 | def create(self, vals): 141 | return super(wh_internal, self).create(vals) 142 | -------------------------------------------------------------------------------- /addons/warehouse/wizard/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import save_bom 3 | import stock_transceive_wizard 4 | import lot_track_wizard 5 | import stock_transceive_collect_wizard 6 | -------------------------------------------------------------------------------- /addons/warehouse/wizard/lot_track_wizard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import date, timedelta 4 | from openerp import models, fields, api 5 | 6 | 7 | class report_lot_track_wizard(models.TransientModel): 8 | _name = 'report.lot.track.wizard' 9 | 10 | @api.model 11 | def _default_date_start(self): 12 | return date.today().replace(day=1).strftime('%Y-%m-%d') 13 | 14 | @api.model 15 | def _default_date_end(self): 16 | now = date.today() 17 | next_month = now.month == 12 and now.replace(year=now.year + 1, 18 | month=1, day=1) or now.replace(month=now.month + 1, day=1) 19 | 20 | return (next_month - timedelta(days=1)).strftime('%Y-%m-%d') 21 | 22 | date_start = fields.Date(u'开始日期', default=_default_date_start) 23 | date_end = fields.Date(u'结束日期', default=_default_date_end) 24 | warehouse = fields.Char(u'仓库') 25 | goods = fields.Char(u'产品') 26 | 27 | @api.one 28 | @api.onchange('date_start', 'date_end') 29 | def onchange_date(self): 30 | if self.date_start and self.date_end and self.date_end < self.date_start: 31 | return {'warning': { 32 | 'title': u'错误', 33 | 'message': u'结束日期不可以小于开始日期' 34 | }, 'value': {'date_end': self.date_start}} 35 | 36 | return {} 37 | 38 | @api.multi 39 | def open_report(self): 40 | return { 41 | 'type': 'ir.actions.act_window', 42 | 'res_model': 'report.lot.track', 43 | 'view_mode': 'tree', 44 | 'name': u'序列号跟踪表', 45 | 'context': self.read(['date_start', 'date_end', 'warehouse', 'goods'])[0], 46 | } 47 | -------------------------------------------------------------------------------- /addons/warehouse/wizard/lot_track_wizard_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | report.lot.track.wizard.form 6 | report.lot.track.wizard 7 | 8 | 9 | 10 | 11 | 12 | 13 | - 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /addons/warehouse/wizard/save_bom.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from openerp import models, fields, api 3 | 4 | 5 | class save_bom_memory(models.TransientModel): 6 | _name = 'save.bom.memory' 7 | 8 | name = fields.Char(u'模板名称') 9 | 10 | @api.multi 11 | def save_bom(self): 12 | for bom in self: 13 | models = self.env[self.env.context.get('active_model')].browse(self.env.context.get('active_ids')) 14 | return models.save_bom(bom.name) 15 | -------------------------------------------------------------------------------- /addons/warehouse/wizard/save_bom_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | save.bom.memory.form 6 | save.bom.memory 7 | 8 | 9 | 10 | 为你新的模板命名 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /addons/warehouse/wizard/stock_transceive_collect_wizard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import date, timedelta 4 | from openerp import models, fields, api 5 | 6 | 7 | class report_stock_transceive_collect_wizard(models.TransientModel): 8 | _name = 'report.stock.transceive.collect.wizard' 9 | 10 | @api.model 11 | def _default_date_start(self): 12 | return date.today().replace(day=1).strftime('%Y-%m-%d') 13 | 14 | @api.model 15 | def _default_date_end(self): 16 | now = date.today() 17 | next_month = now.month == 12 and now.replace(year=now.year + 1, 18 | month=1, day=1) or now.replace(month=now.month + 1, day=1) 19 | 20 | return (next_month - timedelta(days=1)).strftime('%Y-%m-%d') 21 | 22 | date_start = fields.Date(u'开始日期', default=_default_date_start) 23 | date_end = fields.Date(u'结束日期', default=_default_date_end) 24 | warehouse = fields.Char(u'仓库') 25 | goods = fields.Char(u'产品') 26 | 27 | @api.one 28 | @api.onchange('date_start', 'date_end') 29 | def onchange_date(self): 30 | if self.date_start and self.date_end and self.date_end < self.date_start: 31 | return {'warning': { 32 | 'title': u'错误', 33 | 'message': u'结束日期不可以小于开始日期' 34 | }, 'value': {'date_end': self.date_start}} 35 | 36 | return {} 37 | 38 | @api.multi 39 | def open_report(self): 40 | return { 41 | 'type': 'ir.actions.act_window', 42 | 'res_model': 'report.stock.transceive.collect', 43 | 'view_mode': 'tree', 44 | 'name': u'商品收发汇总表', 45 | 'context': self.read(['date_start', 'date_end', 'warehouse', 'goods'])[0], 46 | } 47 | -------------------------------------------------------------------------------- /addons/warehouse/wizard/stock_transceive_collect_wizard_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | report.stock.transceive.collect.wizard.form 6 | report.stock.transceive.collect.wizard 7 | 8 | 9 | 10 | 11 | 12 | 13 | - 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /addons/warehouse/wizard/stock_transceive_wizard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import date, timedelta 4 | from openerp import models, fields, api 5 | 6 | 7 | class report_stock_transceive_wizard(models.TransientModel): 8 | _name = 'report.stock.transceive.wizard' 9 | 10 | @api.model 11 | def _default_date_start(self): 12 | return date.today().replace(day=1).strftime('%Y-%m-%d') 13 | 14 | @api.model 15 | def _default_date_end(self): 16 | now = date.today() 17 | next_month = now.month == 12 and now.replace(year=now.year + 1, 18 | month=1, day=1) or now.replace(month=now.month + 1, day=1) 19 | 20 | return (next_month - timedelta(days=1)).strftime('%Y-%m-%d') 21 | 22 | date_start = fields.Date(u'开始日期', default=_default_date_start) 23 | date_end = fields.Date(u'结束日期', default=_default_date_end) 24 | warehouse = fields.Char(u'仓库') 25 | goods = fields.Char(u'产品') 26 | 27 | @api.one 28 | @api.onchange('date_start', 'date_end') 29 | def onchange_date(self): 30 | if self.date_start and self.date_end and self.date_end < self.date_start: 31 | return {'warning': { 32 | 'title': u'错误', 33 | 'message': u'结束日期不可以小于开始日期' 34 | }, 'value': {'date_end': self.date_start}} 35 | 36 | return {} 37 | 38 | @api.multi 39 | def open_report(self): 40 | return { 41 | 'type': 'ir.actions.act_window', 42 | 'res_model': 'report.stock.transceive', 43 | 'view_mode': 'tree', 44 | 'name': u'商品收发明细表', 45 | 'context': self.read(['date_start', 'date_end', 'warehouse', 'goods'])[0], 46 | } 47 | -------------------------------------------------------------------------------- /addons/warehouse/wizard/stock_transceive_wizard_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | report.stock.transceive.wizard.form 6 | report.stock.transceive.wizard 7 | 8 | 9 | 10 | 11 | 12 | 13 | - 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /extra/web_editable_list_length/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /extra/web_editable_list_length/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # This file is part of web_readonly_bypass, 5 | # an Odoo module. 6 | # 7 | # Copyright (c) 2015 ACSONE SA/NV () 8 | # 9 | # web_readonly_bypass is free software: 10 | # you can redistribute it and/or modify it under the terms of the GNU 11 | # Affero General Public License as published by the Free Software 12 | # Foundation,either version 3 of the License, or (at your option) any 13 | # later version. 14 | # 15 | # web_readonly_bypass is distributed 16 | # in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 17 | # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 18 | # PURPOSE. See the GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with web_readonly_bypass. 22 | # If not, see . 23 | # 24 | ############################################################################## 25 | { 26 | 'name': 'Web Editable List Length', 27 | 'version': '0.1', 28 | "author": "zhengXiang", 29 | "website": "http://www.osbzr.com", 30 | 'category': 'Technical Settings', 31 | 'depends': [ 32 | 'web', 33 | ], 34 | 'description': 35 | """ 36 | form视图中,直接在one2many字段上定义options="{'list_length': 1}" 37 | """, 38 | 'data': [ 39 | 'views/assets_backend.xml', 40 | ], 41 | 'installable': True, 42 | 'auto_install': False, 43 | } 44 | -------------------------------------------------------------------------------- /extra/web_editable_list_length/static/src/js/list.js: -------------------------------------------------------------------------------- 1 | openerp.web_editable_list_length = function(instance) { 2 | instance.web.ListView.include({ 3 | cancel_edition: function(ids) { 4 | var options_length = this.get_options_length(); 5 | if (options_length) { 6 | 7 | var to_delete = this.records.find(function (r) { 8 | return !r.get('id'); 9 | }); 10 | 11 | var need_to_delete = to_delete? 1 : 0; 12 | if (this.records.length - need_to_delete < options_length) { 13 | this.show_add_button('fast'); 14 | }; 15 | }; 16 | 17 | return this._super.apply(this, arguments); 18 | }, 19 | do_delete: function(ids) { 20 | var options_length = this.get_options_length(); 21 | if (options_length) { 22 | if (this.records.length - 1 < options_length) { 23 | this.show_add_button('fast'); 24 | }; 25 | }; 26 | 27 | return this._super.apply(this, arguments); 28 | }, 29 | start_edition: function(record, options){ 30 | var self = this, 31 | res = this._super.apply(this, arguments); 32 | 33 | return res.then(function() { 34 | var options_length = self.get_options_length(); 35 | if (options_length) { 36 | if (self.records.length >= options_length) { 37 | self.hide_add_button('fast'); 38 | }; 39 | }; 40 | }); 41 | }, 42 | get_options_length: function() { 43 | if (this.ViewManager && this.ViewManager.ActionManager) { 44 | var actionManager = this.ViewManager.ActionManager, 45 | options = actionManager.options; 46 | 47 | if (!_.isUndefined(options) && !_.isUndefined(options.list_length)) { 48 | return options.list_length; 49 | } 50 | } 51 | return false 52 | }, 53 | show_add_button: function() { 54 | this.$el.find('.oe_form_field_one2many_list_row_add').show(); 55 | }, 56 | hide_add_button: function() { 57 | this.$el.find('.oe_form_field_one2many_list_row_add').hide(); 58 | }, 59 | }); 60 | 61 | instance.web.ListView.List.include({ 62 | pad_table_to: function(count) { 63 | this._super.apply(this, arguments); 64 | 65 | var self = this; 66 | setTimeout(function() { 67 | var options_length = self.view.get_options_length(); 68 | if (options_length && self.records.length >= options_length) { 69 | self.view.hide_add_button('fast'); 70 | } 71 | }) 72 | }, 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /extra/web_editable_list_length/views/assets_backend.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extra/web_editable_open_dialog/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /extra/web_editable_open_dialog/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # This file is part of web_readonly_bypass, 5 | # an Odoo module. 6 | # 7 | # Copyright (c) 2015 ACSONE SA/NV () 8 | # 9 | # web_readonly_bypass is free software: 10 | # you can redistribute it and/or modify it under the terms of the GNU 11 | # Affero General Public License as published by the Free Software 12 | # Foundation,either version 3 of the License, or (at your option) any 13 | # later version. 14 | # 15 | # web_readonly_bypass is distributed 16 | # in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 17 | # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 18 | # PURPOSE. See the GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with web_readonly_bypass. 22 | # If not, see . 23 | # 24 | ############################################################################## 25 | { 26 | 'name': 'Web Editalbe Open Dialog', 27 | 'version': '0.1', 28 | "author": "zhengXiang", 29 | "website": "http://www.osbzr.com", 30 | 'category': 'Technical Settings', 31 | 'depends': [ 32 | 'web', 33 | ], 34 | 'data': [ 35 | 'views/assets_backend.xml', 36 | ], 37 | 'description': 38 | """ 39 | """, 40 | 'installable': True, 41 | 'auto_install': False, 42 | } 43 | -------------------------------------------------------------------------------- /extra/web_editable_open_dialog/static/src/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | a.open_dialog { 3 | position: absolute; 4 | left: 6px; 5 | top: 1px; 6 | } 7 | 8 | span.readonly_open_dialog { 9 | cursor: pointer; 10 | } -------------------------------------------------------------------------------- /extra/web_editable_open_dialog/static/src/js/dialog.js: -------------------------------------------------------------------------------- 1 | openerp.web_editable_open_dialog = function(instance) { 2 | instance.web.ListView.include({ 3 | start_edition: function(record, options) { 4 | var self = this, 5 | res = this._super.apply(this, arguments); 6 | 7 | // console.warn(self.columns); 8 | return res.then(function() { 9 | _.each(self.columns, function(column) { 10 | if (column.options && _.isString(column.options) && column.options.indexOf('open_dialog') != -1) { 11 | if (column.options.indexOf('set_one2many_readonly') != -1) { 12 | var $td = self.$el.find('.oe_form_container [data-fieldname=' + column.id + ']'), 13 | current_field = self.editor.form.fields[column.id]; 14 | 15 | console.warn('column', self, column); 16 | $td.addClass('readonly_open_dialog'); 17 | $td.click(function(e) { 18 | e.preventDefault(); 19 | e.stopPropagation(); 20 | 21 | if (current_field.get('readonly')) { 22 | self.do_open_one2many_popup(instance.web.py_eval(column.options || '{}')); 23 | }; 24 | }); 25 | } else if (column.options.indexOf('set_one2many') != -1) { 26 | var dialog = self.$el.find('.oe_form_container [data-fieldname=' + column.id + '] a.open_dialog'); 27 | if (dialog.length === 0) { 28 | dialog = $("...").click(function(e) { 29 | e.preventDefault(); 30 | self.do_open_one2many_popup(instance.web.py_eval(column.options || '{}')); 31 | }); 32 | 33 | self.$el.find('.oe_form_container [data-fieldname=' + column.id + ']').append(dialog) 34 | }; 35 | } 36 | }; 37 | }) 38 | }); 39 | }, 40 | do_open_one2many_popup: function(options) { 41 | var self = this, 42 | field_column = false, 43 | notify = instance.web.notification, 44 | pop = new instance.web.form.FormOpenPopup(self); 45 | 46 | if (_.isUndefined(options.open_dialog.field)) { 47 | field_column = column; 48 | } else { 49 | field_column = _.find(self.columns, function(column) { return column.id === options.open_dialog.field}); 50 | } 51 | 52 | if (_.isUndefined(field_column)) { 53 | return notify.warn('错误', options.open_dialog.field + '字段需要在视图中定义'); 54 | } 55 | 56 | if (_.isUndefined(options.open_dialog.view_id)) { 57 | return notify.warn('错误', 'options中需要定义view_id来指定具体的视图'); 58 | } 59 | 60 | var views = options.open_dialog.view_id.split('.'); 61 | if (views.length != 2) { 62 | return notify.warn('错误', 'options中定义的视图id需要指定具体的模块名称'); 63 | } 64 | 65 | var field = self.editor.form.fields[field_column.id], 66 | history_value = field.get_value(); 67 | 68 | var context = self.dataset.get_context(); 69 | try { 70 | if (!_.isUndefined(options.open_dialog.context) && _.isArray(options.open_dialog.context)) { 71 | _.each(options.open_dialog.context, function(field_name) { 72 | var temp_context = {}, 73 | field_value = self.editor.form.fields[field_name].get_value() 74 | 75 | if (!field_value) { 76 | throw '请先给' + field_name + '字段赋值'; 77 | } 78 | 79 | temp_context[field_name] = field_value; 80 | context.add(temp_context); 81 | }) 82 | }; 83 | } catch(e) { 84 | return notify.notify('错误', e); 85 | } 86 | 87 | new instance.web.Model('ir.model.data').call('get_object_reference', views).then(function(view_id) { 88 | pop.show_element( 89 | self.model, 90 | false, 91 | context, 92 | { 93 | view_id: view_id[1], 94 | create_function: function(data) { 95 | if (!_.isUndefined(options.open_dialog.compute_field)) { 96 | var compute_field = options.open_dialog.compute_field, 97 | $compute_field = pop.$el.find('td.oe_list_footer[data-field=' + compute_field + ']'), 98 | compute_result = parseFloat($compute_field.text()); 99 | 100 | if (_.isNaN(compute_field)) { 101 | notify.warn('错误', '需要统计计算的字段没有统计数据或者无法被合计'); 102 | } else { 103 | self.editor.form.fields[compute_field].set_value(compute_result); 104 | }; 105 | }; 106 | field.set_value(history_value.concat(data[field_column.id])); 107 | pop.check_exit(); 108 | return $.Deferred(); 109 | } 110 | } 111 | ); 112 | 113 | var set_pop_value = function() { 114 | try { 115 | console.warn('123213213', history_value); 116 | pop.view_form.fields[field_column.id].set_value(history_value); 117 | } catch(e) { 118 | setTimeout(set_pop_value, 100); 119 | } 120 | }; 121 | 122 | // TODO 使用setTimeout的方式来等待pop加载完毕,不是很好的方法,需要找到一个更好的方法 123 | setTimeout(set_pop_value, 100); 124 | }); 125 | }, 126 | }); 127 | } -------------------------------------------------------------------------------- /extra/web_editable_open_dialog/views/assets_backend.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /extra/web_float_limit/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /extra/web_float_limit/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # This file is part of web_readonly_bypass, 5 | # an Odoo module. 6 | # 7 | # Copyright (c) 2015 ACSONE SA/NV () 8 | # 9 | # web_readonly_bypass is free software: 10 | # you can redistribute it and/or modify it under the terms of the GNU 11 | # Affero General Public License as published by the Free Software 12 | # Foundation,either version 3 of the License, or (at your option) any 13 | # later version. 14 | # 15 | # web_readonly_bypass is distributed 16 | # in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 17 | # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 18 | # PURPOSE. See the GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with web_readonly_bypass. 22 | # If not, see . 23 | # 24 | ############################################################################## 25 | { 26 | 'name': 'Web Float Limit', 27 | 'version': '0.1', 28 | "author": "zhengXiang", 29 | "website": "http://www.osbzr.com", 30 | 'category': 'Technical Settings', 31 | 'depends': [ 32 | 'web', 33 | ], 34 | 'data': [ 35 | 'views/assets_backend.xml', 36 | ], 37 | 'description': 38 | """ 39 | 使用一个float字段的值限制另外一个float字段的最大值 40 | 41 | """, 42 | 'installable': True, 43 | 'auto_install': False, 44 | } 45 | -------------------------------------------------------------------------------- /extra/web_float_limit/static/src/js/limit.js: -------------------------------------------------------------------------------- 1 | openerp.web_float_limit = function(instance) { 2 | instance.web.form.widgets = instance.web.form.widgets.extend({ 3 | 'float_limit' : 'instance.web.form.FieldFloatLimit', 4 | }); 5 | 6 | instance.web.form.FieldFloatLimit = instance.web.form.FieldFloat.extend({ 7 | is_valid: function() { 8 | res = this._super.apply(this, arguments); 9 | var notify = instance.web.notification; 10 | if (res && this.view && this.view.fields && !_.isUndefined(this.view.fields[this.options.field])) { 11 | var max_float = this.view.fields[this.options.field].get_value(); 12 | console.warn('max_float', max_float); 13 | if (max_float > 0 && parseFloat(this.$('input:first').val()) > max_float) { 14 | this.view.float_limit_desc = '当前数量已经超出了最大规定数量' + max_float; 15 | return false; 16 | } 17 | } 18 | 19 | return res 20 | } 21 | }) 22 | 23 | instance.web.FormView.include({ 24 | on_invalid: function() { 25 | if (this.float_limit_desc) { 26 | this.do_warn('错误', this.float_limit_desc); 27 | } else { 28 | this._super.apply(this, arguments); 29 | } 30 | }, 31 | }) 32 | 33 | }; -------------------------------------------------------------------------------- /extra/web_float_limit/views/assets_backend.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extra/web_menu_create/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import ir_ui_view 3 | -------------------------------------------------------------------------------- /extra/web_menu_create/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # This file is part of web_readonly_bypass, 5 | # an Odoo module. 6 | # 7 | # Copyright (c) 2015 ACSONE SA/NV () 8 | # 9 | # web_readonly_bypass is free software: 10 | # you can redistribute it and/or modify it under the terms of the GNU 11 | # Affero General Public License as published by the Free Software 12 | # Foundation,either version 3 of the License, or (at your option) any 13 | # later version. 14 | # 15 | # web_readonly_bypass is distributed 16 | # in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 17 | # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 18 | # PURPOSE. See the GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with web_readonly_bypass. 22 | # If not, see . 23 | # 24 | ############################################################################## 25 | { 26 | 'name': 'Web Menu Create', 27 | 'version': '0.1', 28 | "author": "zhengXiang", 29 | "website": "http://www.osbzr.com", 30 | 'category': 'Technical Settings', 31 | 'depends': [ 32 | 'web', 33 | ], 34 | 'description': 35 | """ 36 | 直接在菜单上面显示新建按钮的快速通道,需要将菜单记录中的create_tag设置位True 37 | """, 38 | 'data': [ 39 | 'views/assets_backend.xml', 40 | ], 41 | 'installable': True, 42 | 'auto_install': False, 43 | } 44 | -------------------------------------------------------------------------------- /extra/web_menu_create/ir_ui_view.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from openerp.osv import osv 4 | from openerp.osv import fields 5 | 6 | 7 | class ir_ui_menu(osv.osv): 8 | _inherit = 'ir.ui.menu' 9 | 10 | _columns = { 11 | 'create_tag': fields.boolean(u'直接创建'), 12 | } 13 | 14 | def load_create_tag(self, cr, uid, ids, context=None): 15 | return [menu.id for menu in self.browse(cr, uid, ids, context=context) if menu.create_tag] 16 | -------------------------------------------------------------------------------- /extra/web_menu_create/static/src/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | span.menu-create-tag { 3 | overflow: hidden;; 4 | display: inline-block; 5 | max-width: 85%; 6 | margin-top: 1px; 7 | margin-left: 12px; 8 | background-color: #00FFCC; 9 | padding: 0px, 4px; 10 | border-radius: 2.5px; 11 | color: white; 12 | font-size: 12px; 13 | } -------------------------------------------------------------------------------- /extra/web_menu_create/static/src/js/menu.js: -------------------------------------------------------------------------------- 1 | openerp.web_menu_create = function(instance) { 2 | instance.web.Menu.include({ 3 | init: function() { 4 | this._super.apply(this, arguments); 5 | var self = this; 6 | this.on('menu_bound', this, function() { 7 | var $all_menus = self.$el.parents('body').find('.oe_webclient').find('[data-menu]'); 8 | var all_menu_ids = _.map($all_menus, function (menu) {return parseInt($(menu).attr('data-menu'), 10);}); 9 | 10 | this.do_load_create_tag(all_menu_ids); 11 | }); 12 | }, 13 | do_load_create_tag: function(menu_ids) { 14 | var self = this; 15 | menu_ids = _.compact(menu_ids); 16 | if (_.isEmpty(menu_ids)) { 17 | return $.when(); 18 | } 19 | 20 | return new instance.web.Model('ir.ui.menu').call('load_create_tag', [menu_ids]).then(function(result) { 21 | _.each(result, function(menu_id) { 22 | var $item = self.$secondary_menus.find('a[data-menu="' + menu_id + '"]'); 23 | $item.append("新建"); 24 | }); 25 | 26 | self.$secondary_menus.find('span.menu-create-tag').click(function(e) { 27 | var current = $(this), 28 | action_id = $(this).closest('a').data('action-id'), 29 | menu_id = $(this).closest('a').data('menu'); 30 | 31 | if (action_id) { 32 | e.preventDefault(); 33 | e.stopPropagation(); 34 | 35 | self.open_menu(menu_id); 36 | self.on_create_tag_action(action_id); 37 | } 38 | }) 39 | }); 40 | }, 41 | on_create_tag_action: function(action_id) { 42 | var self = this, 43 | parent = this.__parentedParent; 44 | 45 | return parent.menu_dm.add(parent.rpc("/web/action/load", { action_id: action_id })) 46 | .then(function (result) { 47 | result.view_mode = 'form'; 48 | // result.views = [[false, 'form']] 49 | var form_views = _.find(result.views, function(view) { return view[1] == 'form'}) 50 | result.views = (_.isUndefined(form_views))? [[false, 'form']] : [form_views]; 51 | 52 | return parent.action_mutex.exec(function() { 53 | var completed = $.Deferred(); 54 | $.when(parent.action_manager.do_action(result, { 55 | clear_breadcrumbs: true, 56 | action_menu_id: parent.menu.current_menu, 57 | })).always(function() { 58 | completed.resolve(); 59 | }); 60 | setTimeout(function() { 61 | completed.resolve(); 62 | }, 2000); 63 | return completed; 64 | }); 65 | }); 66 | }, 67 | }); 68 | }; -------------------------------------------------------------------------------- /extra/web_menu_create/views/assets_backend.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /extra/web_one2many_reconstruction/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /extra/web_one2many_reconstruction/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # This file is part of web_readonly_bypass, 5 | # an Odoo module. 6 | # 7 | # Copyright (c) 2015 ACSONE SA/NV () 8 | # 9 | # web_readonly_bypass is free software: 10 | # you can redistribute it and/or modify it under the terms of the GNU 11 | # Affero General Public License as published by the Free Software 12 | # Foundation,either version 3 of the License, or (at your option) any 13 | # later version. 14 | # 15 | # web_readonly_bypass is distributed 16 | # in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 17 | # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 18 | # PURPOSE. See the GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with web_readonly_bypass. 22 | # If not, see . 23 | # 24 | ############################################################################## 25 | { 26 | 'name': 'Web One2many Reconstruction', 27 | 'version': '0.1', 28 | "author": "zhengXiang", 29 | "website": "http://www.osbzr.com", 30 | 'category': 'Technical Settings', 31 | 'depends': [ 32 | 'web', 33 | ], 34 | 'data': [ 35 | 'views/assets_backend.xml', 36 | ], 37 | 'description': 38 | """ 39 | 重写one2many字段中显示的列表界面 40 | """, 41 | 'qweb': [ 42 | 'static/src/xml/one2many.xml', 43 | ], 44 | 'installable': True, 45 | 'auto_install': False, 46 | } 47 | -------------------------------------------------------------------------------- /extra/web_one2many_reconstruction/static/src/css/style.css: -------------------------------------------------------------------------------- 1 | table.reconstruction_table { 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /extra/web_one2many_reconstruction/static/src/js/one2many.js: -------------------------------------------------------------------------------- 1 | openerp.web_one2many_reconstruction = function(instance) { 2 | var QWeb = instance.web.qweb; 3 | 4 | instance.web.form.FieldOne2Many_Reconstruction = instance.web.form.AbstractField.extend({ 5 | init: function(field_manager, node) { 6 | this._super.apply(this, arguments); 7 | 8 | this.columns = []; 9 | 10 | this.dataset = new instance.web.form.One2ManyDataSet(this, this.field.relation); 11 | this.dataset.o2m = this; 12 | this.dataset.parent_view = this.view; 13 | this.dataset.child_name = this.name; 14 | this.context = this.build_context().eval() 15 | 16 | this.on('change:effective_readonly', this, this.change_editable); 17 | }, 18 | loading_views: function() { 19 | var self = this, 20 | view_loaded_def, 21 | tree_view = (self.field.views || {}).tree; 22 | 23 | if (_.isUndefined(tree_view)) { 24 | view_loaded_def = instance.web.fields_view_get({ 25 | 'model': self.dataset._model, 26 | 'view_id': false, 27 | 'view_type': 'tree', 28 | 'toolbar': false, 29 | 'context': self.context, 30 | }); 31 | } else { 32 | view_loaded_def = $.Deferred(); 33 | $.when().then(function() { 34 | view_loaded_def.resolve(tree_view); 35 | }); 36 | }; 37 | 38 | return this.alive(view_loaded_def).done(function(r) { 39 | self.loading_tree_view(r); 40 | }); 41 | }, 42 | 43 | loading_tree_view: function(data) { 44 | var self = this; 45 | self.fields_view = data, 46 | self.name = "" + self.fields_view.arch.attrs.string; 47 | 48 | self.setup_columns(self.fields_view.arch.children, self.fields_view.fields); 49 | // console.warn(QWeb.render('web_one2many_reconstruction.one2many', {columns: self.columns})); 50 | self.$el.html(QWeb.render('web_one2many_reconstruction.one2many', {columns: self.columns})) 51 | console.warn(self.$el); 52 | 53 | console.warn(self.columns); 54 | }, 55 | 56 | setup_columns: function(children, fields) { 57 | var self = this, 58 | registry = instance.web.list.columns; 59 | 60 | this.columns.splice(0, this.columns.length - 1); 61 | this.columns = _.map(children, function(field) { 62 | var id = field.attrs.name; 63 | return registry.for_(id, fields[id], field); 64 | }); 65 | 66 | this.visible_columns = _.filter(this.columns, function (column) { 67 | return column.invisible !== '1'; 68 | }); 69 | }, 70 | 71 | change_editable: function() { 72 | var self = this, 73 | readonly = this.get('effective_readonly'); 74 | 75 | console.warn('readonly', readonly); 76 | }, 77 | 78 | render_value: function() { 79 | this.loading_views(); 80 | }, 81 | }); 82 | 83 | instance.web.form.widgets = instance.web.form.widgets.extend({ 84 | 'one2many_reconstruction' : 'instance.web.form.FieldOne2Many_Reconstruction', 85 | }); 86 | }; -------------------------------------------------------------------------------- /extra/web_one2many_reconstruction/static/src/xml/one2many.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | hello 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /extra/web_one2many_reconstruction/views/assets_backend.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /extra/web_readonly_bypass/README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg 2 | :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html 3 | :alt: License: AGPL-3 4 | 5 | ================ 6 | Read Only ByPass 7 | ================ 8 | 9 | This module provides a solution to the problem of the interaction between 10 | 'readonly' attribute and 'on_change' attribute when used together. It allows 11 | saving onchange modifications to readonly fields. 12 | 13 | Behavior: add readonly fields changed by `on_change` methods to the values 14 | passed to write or create. If `readonly_by_pass` is in the context and 15 | True then it will by pass readonly fields and save its data provide by onchange 16 | method. 17 | 18 | Usage 19 | ===== 20 | 21 | This module changes the behaviour of Odoo by propagating 22 | on_change modifications to readonly fields to the backend create and write 23 | methods. 24 | 25 | To change that behavior you have to set context on ``ur.actions.act_window``:: 26 | 27 | 28 | {'readonly_by_pass': True} 29 | 30 | 31 | or by telling fields allowed to change:: 32 | 33 | 34 | 35 | {'readonly_by_pass': ['readonly_field_1', 'readonly_field_2',]} 36 | 37 | 38 | 39 | On one2many fields, you can also pass the context in the field definition: 40 | 41 | 42 | 43 | .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas 44 | :alt: Try me on Runbot 45 | :target: https://runbot.odoo-community.org/runbot/162/8.0 46 | 47 | 48 | Bug Tracker 49 | =========== 50 | 51 | Bugs are tracked on `GitHub Issues `_. 52 | In case of trouble, please check there if your issue has already been reported. 53 | If you spotted it first, help us smashing it by providing a detailed and welcomed feedback 54 | `here `_. 55 | 56 | 57 | Credits 58 | ======= 59 | 60 | Contributors 61 | ------------ 62 | 63 | * Jonathan Nemry 64 | * Laetitia Gangloff 65 | * Pierre Verkest 66 | 67 | Maintainer 68 | ---------- 69 | 70 | .. image:: https://odoo-community.org/logo.png 71 | :alt: Odoo Community Association 72 | :target: https://odoo-community.org 73 | 74 | This module is maintained by the OCA. 75 | 76 | OCA, or the Odoo Community Association, is a nonprofit organization whose 77 | mission is to support the collaborative development of Odoo features and 78 | promote its widespread use. 79 | 80 | To contribute to this module, please visit http://odoo-community.org. 81 | -------------------------------------------------------------------------------- /extra/web_readonly_bypass/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /extra/web_readonly_bypass/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # This file is part of web_readonly_bypass, 5 | # an Odoo module. 6 | # 7 | # Copyright (c) 2015 ACSONE SA/NV () 8 | # 9 | # web_readonly_bypass is free software: 10 | # you can redistribute it and/or modify it under the terms of the GNU 11 | # Affero General Public License as published by the Free Software 12 | # Foundation,either version 3 of the License, or (at your option) any 13 | # later version. 14 | # 15 | # web_readonly_bypass is distributed 16 | # in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 17 | # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 18 | # PURPOSE. See the GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with web_readonly_bypass. 22 | # If not, see . 23 | # 24 | ############################################################################## 25 | { 26 | 'name': 'Read Only ByPass', 27 | 'version': '8.0.1.0.1', 28 | "author": "ACSONE SA/NV, Odoo Community Association (OCA)", 29 | "maintainer": "ACSONE SA/NV,Odoo Community Association (OCA)", 30 | "website": "http://www.acsone.eu", 31 | 'category': 'Technical Settings', 32 | 'depends': [ 33 | 'web', 34 | ], 35 | 'summary': 'Allow to save onchange modifications to readonly fields', 36 | 'data': [ 37 | 'views/readonly_bypass.xml', 38 | ], 39 | 'installable': True, 40 | 'auto_install': False, 41 | } 42 | -------------------------------------------------------------------------------- /extra/web_readonly_bypass/views/readonly_bypass.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /extra/web_sublist/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /extra/web_sublist/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # This file is part of web_readonly_bypass, 5 | # an Odoo module. 6 | # 7 | # Copyright (c) 2015 ACSONE SA/NV () 8 | # 9 | # web_readonly_bypass is free software: 10 | # you can redistribute it and/or modify it under the terms of the GNU 11 | # Affero General Public License as published by the Free Software 12 | # Foundation,either version 3 of the License, or (at your option) any 13 | # later version. 14 | # 15 | # web_readonly_bypass is distributed 16 | # in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 17 | # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 18 | # PURPOSE. See the GNU Affero General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Affero General Public License 21 | # along with web_readonly_bypass. 22 | # If not, see . 23 | # 24 | ############################################################################## 25 | { 26 | 'name': 'Web SubList', 27 | 'version': '0.1', 28 | "author": "zhengXiang", 29 | "website": "http://www.osbzr.com", 30 | 'category': 'Technical Settings', 31 | 'depends': [ 32 | 'web', 33 | ], 34 | 'data': [ 35 | 'views/assets_backend.xml', 36 | ], 37 | 'description': 38 | """ 39 | 在tree视图中将一个one2many字段渲染成一个子table(不可以在editable中使用) 40 | 41 | """, 42 | 'qweb': [ 43 | 'static/src/xml/sublist.xml', 44 | ], 45 | 'installable': True, 46 | 'auto_install': False, 47 | } 48 | -------------------------------------------------------------------------------- /extra/web_sublist/static/src/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | .openerp table.oe_list_content td:not(.oe_list_field_relation_sublist), 3 | .openerp div.oe_form_container span.oe_form_field 4 | { 5 | vertical-align: middle !important; 6 | } 7 | 8 | .openerp td.oe_list_field_relation_sublist { 9 | padding: 3px 0px !important; 10 | } 11 | 12 | .oe_list_field_relation_sublist table { 13 | width: 100%; 14 | } 15 | 16 | 17 | 18 | /*tr .oe_list_field_relation_sublist tr:nth-child(2n) { 19 | background-color: white; 20 | }*/ -------------------------------------------------------------------------------- /extra/web_sublist/static/src/js/sublist.js: -------------------------------------------------------------------------------- 1 | openerp.web_sublist = function(instance) { 2 | var QWeb = instance.web.qweb; 3 | var RELATION_TAG = 'RELATION_SUBLIST_MULTI'; 4 | 5 | instance.web_sublist.relationSublist = instance.web.list.Column.extend({ 6 | _format: function(row_data, options) { 7 | if (this.widget === 'relation_sublist') { 8 | 9 | var values = row_data[this.id]['value'], 10 | options = instance.web.py_eval(this.options || '{}'), 11 | field_value = _.map(_.map(values, function(value) { return value[options.field]; }), function(value) { 12 | return _.isArray(value)? value[1]: value; 13 | }); 14 | 15 | // 通过without去除掉undefined的值 16 | return QWeb.render('web_sublist.sublist', {'values': _.without(field_value, undefined)}).trim(); 17 | } else { 18 | return this._super(row_data, options); 19 | }; 20 | 21 | }, 22 | }); 23 | 24 | instance.web.ListView.Groups.include({ 25 | render_dataset: function(dataset) { 26 | var self = this, 27 | deferred = this._super.apply(this, arguments); 28 | 29 | return deferred.then(function(list) { 30 | var containers = {} 31 | 32 | // 统计需要定义子列表的字段 33 | _.each(list.columns, function(column) { 34 | if (column.widget === 'relation_sublist') { 35 | var options = instance.web.py_eval(column.options || '{}'); 36 | if (_.isUndefined(containers[column.id])) { 37 | containers[column.id] = {relation: column.relation, fields: [], ids: []}; 38 | } 39 | 40 | containers[column.id]['fields'].push(options.field); 41 | } 42 | }); 43 | 44 | // 统计需要定义子列表字段的ids 45 | self.records.each(function(record) { 46 | _.each(_.keys(containers), function(key) { 47 | containers[key]['ids'] = containers[key]['ids'].concat(record.get(key)); 48 | }); 49 | }); 50 | 51 | // 根据子列表的model、field、ids来取值 52 | _.each(_.keys(containers), function(key) { 53 | var container = containers[key]; 54 | new instance.web.Model(container.relation).call('read', [container.ids, container.fields]).then(function(results) { 55 | self.records.each(function(record) { 56 | var filter_value = _.filter(results, function(result) { 57 | return _.contains(record.get(key), result.id) 58 | }); 59 | record.set(key, filter_value); 60 | 61 | if (filter_value.length > 1) record.set(RELATION_TAG, true); 62 | }); 63 | }) 64 | }); 65 | 66 | return list 67 | }); 68 | } 69 | }) 70 | 71 | instance.web.list.columns.add('field.relation_sublist', 'instance.web_sublist.relationSublist'); 72 | 73 | }; -------------------------------------------------------------------------------- /extra/web_sublist/static/src/xml/sublist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /extra/web_sublist/views/assets_backend.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------
72 | 点击创建资金转账单 73 |
84 | 点击创建其他收入单 85 |
99 | 点击创建其他支出单 100 |
90 | 点击创建核销单 91 |
在盘点完成前,请不要做出入库单据,盘点库存与查询时的系统库存比较计算盈亏,如果库存在查询后又发生了变化,需要重新查询一次获取最新的库存信息
为你新的模板命名