chore: re-initialize git repository for deployment
@@ -0,0 +1,93 @@
|
|||||||
|
# --- Python ---
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
*.egg
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env/
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# --- Django ---
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Collected static (if STATIC_ROOT differs from source)
|
||||||
|
staticfiles/
|
||||||
|
collected_static/
|
||||||
|
|
||||||
|
# Compiled translations (generate via compilemessages)
|
||||||
|
*.mo
|
||||||
|
|
||||||
|
# --- Environment & secrets ---
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
|
||||||
|
# --- IDE / Editor ---
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# --- OS ---
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
desktop.ini
|
||||||
|
|
||||||
|
# --- Cursor / local workflow ---
|
||||||
|
.cursor/
|
||||||
|
.work/
|
||||||
|
|
||||||
|
# --- Test & coverage ---
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# --- Docker (local overrides & volumes) ---
|
||||||
|
docker-compose.override.yml
|
||||||
|
docker-compose.local.yml
|
||||||
|
*.volume/
|
||||||
|
|
||||||
|
# --- User uploads (MEDIA_ROOT) ---
|
||||||
|
# Keep template docs under upload/doc/
|
||||||
|
upload/data/*
|
||||||
|
!upload/data/.gitkeep
|
||||||
|
upload/inventory/*
|
||||||
|
!upload/inventory/.gitkeep
|
||||||
|
upload/project/*
|
||||||
|
!upload/project/.gitkeep
|
||||||
|
upload/offer sheet/*
|
||||||
|
!upload/offer sheet/.gitkeep
|
||||||
|
|
||||||
|
# --- Misc ---
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
.work/
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
FROM python:2.7-slim
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE 1
|
||||||
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
|
||||||
|
# Set work directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
# Using PyMySQL which is pure python, so we might not need gcc/mysql-client-dev
|
||||||
|
COPY Install/requirements.txt /app/Install/requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r Install/requirements.txt
|
||||||
|
|
||||||
|
# Copy project
|
||||||
|
COPY . /app/
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Run server
|
||||||
|
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
-- 销售订单示例数据(依赖 basedata_partner / basedata_material 等基础数据)
|
||||||
|
-- 导入: mysql -uroot -proot mis < Install/SQL/sale_order_sample.sql
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleorder`
|
||||||
|
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
|
||||||
|
SELECT 1,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00001','2025-05-10','2025-05-25','办公设备采购订单','含笔记本电脑及服务器,需上门安装调试。','张经理','13800138001',NULL,'上海市浦东新区张江高科技园区科苑路88号','10',24299.00,0.00,'9',1,17,11
|
||||||
|
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00001');
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleorder`
|
||||||
|
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
|
||||||
|
SELECT 2,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00002','2025-05-15','2025-06-01','配电设备批量采购','项目现场配电改造用材。','李工','13900139002',NULL,'江苏省南京市江宁区将军大道29号','10',6400.00,200.00,'1',1,17,11
|
||||||
|
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00002');
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleorder`
|
||||||
|
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
|
||||||
|
SELECT 3,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00003','2025-05-18','2025-05-22','水果批发季节订单','当季鲜果直采,分批配送。','王采购','13700137003',NULL,'浙江省杭州市余杭区良渚街道物流园A区','10',13962.00,0.00,'0',1,17,11
|
||||||
|
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00003');
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleorder`
|
||||||
|
(`id`,`begin`,`end`,`creator`,`modifier`,`creation`,`modification`,`code`,`order_date`,`deliver_date`,`title`,`description`,`contact`,`phone`,`fax`,`deliver_address`,`invoice_type`,`amount`,`discount_amount`,`status`,`org_id`,`partner_id`,`user_id`)
|
||||||
|
SELECT 4,'2025-05-20','9999-12-31','chengcai','chengcai',NOW(),NOW(),'SO00004','2025-05-20','2025-05-28','劳保用品集中采购','车间一线员工劳保手套补充采购。','赵主任','13600136004',NULL,'安徽省合肥市蜀山区望江西路666号','10',5500.00,0.00,'9',1,17,11
|
||||||
|
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleorder WHERE code='SO00004');
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||||
|
SELECT 1,2.0000,NULL,7150.0000,'0.00',NOW(),0,NULL,1,53,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=1);
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||||
|
SELECT 2,1.0000,NULL,9999.0000,'0.00',NOW(),0,NULL,1,54,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=2);
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||||
|
SELECT 3,20.0000,NULL,320.0000,'0.00',NOW(),0,NULL,2,52,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=3);
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||||
|
SELECT 4,500.0000,NULL,10.9200,'0.00',NOW(),0,NULL,3,40,3,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=4);
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||||
|
SELECT 5,300.0000,NULL,15.9900,'0.00',NOW(),0,NULL,3,41,3,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=5);
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||||
|
SELECT 6,200.0000,NULL,18.5250,'0.00',NOW(),0,NULL,3,42,3,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=6);
|
||||||
|
|
||||||
|
INSERT INTO `sale_saleitem` (`id`,`cnt`,`stock_price`,`sale_price`,`tax`,`create_time`,`status`,`event_time`,`master_id`,`material_id`,`measure_id`,`discount_price`)
|
||||||
|
SELECT 7,1000.0000,NULL,5.5000,'0.00',NOW(),0,NULL,4,5,1,NULL FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sale_saleitem WHERE id=7);
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""导入销售订单示例数据。用法: python Install/load_sale_sample.py"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
if BASE_DIR not in sys.path:
|
||||||
|
sys.path.insert(0, BASE_DIR)
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mis.settings')
|
||||||
|
|
||||||
|
import django
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import transaction
|
||||||
|
from basedata.models import Partner, Material, Organization
|
||||||
|
from sale.models import SaleOrder, SaleItem
|
||||||
|
|
||||||
|
|
||||||
|
def recalc_order_amount(order):
|
||||||
|
total = Decimal('0')
|
||||||
|
for item in SaleItem.objects.filter(master=order):
|
||||||
|
price = item.sale_price or Decimal('0')
|
||||||
|
total += price * item.cnt
|
||||||
|
SaleOrder.objects.filter(pk=order.pk).update(amount=total)
|
||||||
|
order.amount = total
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
SAMPLE_ORDERS = [
|
||||||
|
{
|
||||||
|
'code': 'SO00001',
|
||||||
|
'title': u'办公设备采购订单',
|
||||||
|
'order_date': datetime.date(2025, 5, 10),
|
||||||
|
'deliver_date': datetime.date(2025, 5, 25),
|
||||||
|
'contact': u'张经理',
|
||||||
|
'phone': '13800138001',
|
||||||
|
'deliver_address': u'上海市浦东新区张江高科技园区科苑路88号',
|
||||||
|
'description': u'含笔记本电脑及服务器,需上门安装调试。',
|
||||||
|
'status': '9',
|
||||||
|
'discount_amount': 0,
|
||||||
|
'items': [
|
||||||
|
{'material_code': 'IT5001', 'cnt': 2, 'sale_price': 7150.00},
|
||||||
|
{'material_code': 'IT5002', 'cnt': 1, 'sale_price': 9999.00},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'code': 'SO00002',
|
||||||
|
'title': u'配电设备批量采购',
|
||||||
|
'order_date': datetime.date(2025, 5, 15),
|
||||||
|
'deliver_date': datetime.date(2025, 6, 1),
|
||||||
|
'contact': u'李工',
|
||||||
|
'phone': '13900139002',
|
||||||
|
'deliver_address': u'江苏省南京市江宁区将军大道29号',
|
||||||
|
'description': u'项目现场配电改造用材。',
|
||||||
|
'status': '1',
|
||||||
|
'discount_amount': 200.00,
|
||||||
|
'items': [
|
||||||
|
{'material_code': 'IT9981', 'cnt': 20, 'sale_price': 320.00},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'code': 'SO00003',
|
||||||
|
'title': u'水果批发季节订单',
|
||||||
|
'order_date': datetime.date(2025, 5, 18),
|
||||||
|
'deliver_date': datetime.date(2025, 5, 22),
|
||||||
|
'contact': u'王采购',
|
||||||
|
'phone': '13700137003',
|
||||||
|
'deliver_address': u'浙江省杭州市余杭区良渚街道物流园A区',
|
||||||
|
'description': u'当季鲜果直采,分批配送。',
|
||||||
|
'status': '0',
|
||||||
|
'discount_amount': 0,
|
||||||
|
'items': [
|
||||||
|
{'material_code': 'IT9001', 'cnt': 500, 'sale_price': 11.00},
|
||||||
|
{'material_code': 'IT9002', 'cnt': 300, 'sale_price': 16.00},
|
||||||
|
{'material_code': 'IT9003', 'cnt': 200, 'sale_price': 19.00},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'code': 'SO00004',
|
||||||
|
'title': u'劳保用品集中采购',
|
||||||
|
'order_date': datetime.date(2025, 5, 20),
|
||||||
|
'deliver_date': datetime.date(2025, 5, 28),
|
||||||
|
'contact': u'赵主任',
|
||||||
|
'phone': '13600136004',
|
||||||
|
'deliver_address': u'安徽省合肥市蜀山区望江西路666号',
|
||||||
|
'description': u'车间一线员工劳保手套补充采购。',
|
||||||
|
'status': '9',
|
||||||
|
'discount_amount': 0,
|
||||||
|
'items': [
|
||||||
|
{'material_code': 'IT00005', 'cnt': 1000, 'sale_price': 5.50},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def load_samples():
|
||||||
|
partner = Partner.objects.filter(partner_type='C').first()
|
||||||
|
if not partner:
|
||||||
|
print(u'错误: 未找到客户类型合作伙伴,请先维护基础数据。')
|
||||||
|
return
|
||||||
|
|
||||||
|
user = User.objects.filter(username='chengcai').first() or User.objects.first()
|
||||||
|
org = Organization.objects.first()
|
||||||
|
creator = user.username if user else 'system'
|
||||||
|
today = datetime.date.today()
|
||||||
|
|
||||||
|
created = 0
|
||||||
|
skipped = 0
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for data in SAMPLE_ORDERS:
|
||||||
|
if SaleOrder.objects.filter(code=data['code']).exists():
|
||||||
|
print(u'跳过已存在订单: %s' % data['code'])
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
order = SaleOrder(
|
||||||
|
code=data['code'],
|
||||||
|
title=data['title'],
|
||||||
|
order_date=data['order_date'],
|
||||||
|
deliver_date=data['deliver_date'],
|
||||||
|
partner=partner,
|
||||||
|
org=org,
|
||||||
|
user=user,
|
||||||
|
contact=data['contact'],
|
||||||
|
phone=data['phone'],
|
||||||
|
deliver_address=data['deliver_address'],
|
||||||
|
description=data['description'],
|
||||||
|
invoice_type='10',
|
||||||
|
status=data['status'],
|
||||||
|
discount_amount=data['discount_amount'],
|
||||||
|
amount=0,
|
||||||
|
begin=today,
|
||||||
|
end=datetime.date(9999, 12, 31),
|
||||||
|
creator=creator,
|
||||||
|
modifier=creator,
|
||||||
|
)
|
||||||
|
order.save()
|
||||||
|
|
||||||
|
for item_data in data['items']:
|
||||||
|
material = Material.objects.filter(code=item_data['material_code']).first()
|
||||||
|
if not material:
|
||||||
|
print(u'警告: 物料 %s 不存在,已跳过' % item_data['material_code'])
|
||||||
|
continue
|
||||||
|
item = SaleItem(
|
||||||
|
master=order,
|
||||||
|
material=material,
|
||||||
|
cnt=item_data['cnt'],
|
||||||
|
sale_price=item_data['sale_price'],
|
||||||
|
tax='0.00',
|
||||||
|
)
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
if data['discount_amount'] > 0:
|
||||||
|
order.save()
|
||||||
|
total = recalc_order_amount(order)
|
||||||
|
print(u'已创建: %s %s 金额=%s 状态=%s' % (
|
||||||
|
order.code, order.title, total, order.get_status_display()
|
||||||
|
))
|
||||||
|
created += 1
|
||||||
|
|
||||||
|
print(u'\n完成: 新增 %d 条,跳过 %d 条。' % (created, skipped))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
load_samples()
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Django==1.10.6
|
||||||
|
PyMySQL==0.7.9
|
||||||
|
xlwt==1.0.0
|
||||||
|
xlrd==1.2.0
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# 智捷ERP (SmartJet ERP) 操作手册
|
||||||
|
|
||||||
|
智捷ERP 是一款基于 Django 1.10.6 深度定制的企业资源计划 (ERP) 管理软件。系统经过现代 UI 重构与 Docker 化部署优化,提供销售管理、采购管理、库存管理、组织架构、人力资源及自动化工作流审批等核心功能。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 快速开始
|
||||||
|
|
||||||
|
### 1.1 环境要求
|
||||||
|
- Docker
|
||||||
|
- Docker Compose
|
||||||
|
|
||||||
|
### 1.2 一键部署
|
||||||
|
在项目根目录下执行以下命令,即可启动 Web 服务与 MySQL 数据库:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.full.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 访问地址
|
||||||
|
- **后台管理**: [http://localhost:6010/admin/](http://localhost:6010/admin/)
|
||||||
|
- **数据库服务**: `localhost:6011` (容器内 3306)
|
||||||
|
|
||||||
|
### 1.4 默认账号
|
||||||
|
系统预置了管理员账号用于演示:
|
||||||
|
|
||||||
|
| 角色 | 用户名 | 密码 | 说明 |
|
||||||
|
|------|--------|------|------|
|
||||||
|
| 超级管理员 | xinmi_admin | admin123456 | 全新创建的超级用户,建议首选使用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 核心功能模块
|
||||||
|
|
||||||
|
智捷ERP 采用现代化的“侧边栏 + 高密度工作台”布局,确保业务数据直观可见。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 2.1 仪表盘 (Dashboard)
|
||||||
|
**路径**: `/admin/`
|
||||||
|
**功能概述**: 实时展现企业核心经营指标。
|
||||||
|
- **数据看板**: 销售总额、待办审批、库存预警、系统在线率。
|
||||||
|
- **趋势分析**: 关键指标下方的 Sparkline 动态折线图。
|
||||||
|
- **快捷操作**: 一键新建销售单、采购入库、员工入职。
|
||||||
|
|
||||||
|
### 2.2 核心业务
|
||||||
|
- **销售管理**: `/admin/sale/saleorder/` —— 涵盖报价单、销售订单、收款计划跟踪。
|
||||||
|
- **采购管理**: `/admin/purchase/purchaseorder/` —— 支持采购申请、订单审批、入库确认。
|
||||||
|
- **库存管理**: `/admin/invent/inventory/` —— 实时盘点、低水位报警、批次溯源。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2.3 组织与人力
|
||||||
|
- **组织架构**: `/admin/organ/organization/` —— 维护企业多级部门与岗位树。
|
||||||
|
- **员工档案**: `/admin/basedata/employee/` —— 员工全生命周期管理、五险一金基础配置。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 2.4 流程协同
|
||||||
|
- **审批中心**: `/admin/workflow/instance/` —— 流程实例监控。
|
||||||
|
|
||||||
|
- **待办任务**: `/admin/workflow/todolist/` —— 个人审批任务列表,支持紧急程度分级。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 视觉与体验优化 (Next-Gen UI 4.0)
|
||||||
|
|
||||||
|
本项目已完成 1:1 高保真 UI 复刻,具有以下设计特征:
|
||||||
|
- **高级配色**: 采用 Indigo & Slate 商务配色。
|
||||||
|
- **极致紧凑**: Ultra-Compact 模式,单屏展示数据量提升 40% 以上。
|
||||||
|
- **响应式布局**: 侧边栏固定,超宽表格自动开启内部横向滚动保护。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 技术维护
|
||||||
|
|
||||||
|
### 4.1 数据库初始化
|
||||||
|
数据库初始化 SQL 位于 `Install/SQL/mis.sql`,Docker 启动时会自动挂载并执行。
|
||||||
|
|
||||||
|
### 4.2 静态资源更新
|
||||||
|
若修改了 CSS 或 JS,请在容器内执行:
|
||||||
|
```bash
|
||||||
|
docker compose exec web python manage.py collectstatic --noinput
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 常见问题 (FAQ)
|
||||||
|
|
||||||
|
**Q: 登录时提示“请输入正确的用户名和密码”?**
|
||||||
|
A: 可能是由于默认加密算法版本差异,请通过控制台重置:
|
||||||
|
`docker compose exec web python manage.py changepassword admin`
|
||||||
|
|
||||||
|
**Q: 列表页面字段太多,显示超宽?**
|
||||||
|
A: 系统已开启横向滚动条保护,您可以在表格区域内左右滑动查看完整数据。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
© 2026 智捷ERP (SmartJet ERP) · 让企业管理更智能
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
default_app_config = 'basedata.apps.BaseDataConfig'
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.forms import models
|
||||||
|
from django.forms import fields,TextInput,Textarea
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||||
|
from common import generic
|
||||||
|
from basedata.models import ValueList,ValueListItem,Address,Partner,BankAccount,Project,Measure,Material,Brand,\
|
||||||
|
Category,Warehouse,TechnicalParameterName,TechnicalParameterValue,Trade,ExpenseAccount,Employee,Family,Education,\
|
||||||
|
WorkExperience,ExtraParam,DataImport,Document
|
||||||
|
|
||||||
|
|
||||||
|
class ValueListItemInline(admin.TabularInline):
|
||||||
|
model = ValueListItem
|
||||||
|
exclude = ['group_code']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
|
||||||
|
class ValueListAdmin(generic.BOAdmin):
|
||||||
|
CODE_NUMBER_WIDTH = 3
|
||||||
|
CODE_PREFIX = 'S'
|
||||||
|
list_display = ['code', 'name', 'module', 'status']
|
||||||
|
fields = (('code',),('name',),('module',),('status','init','locked',),('locked_by','lock_time',))
|
||||||
|
raw_id_fields = ['module']
|
||||||
|
readonly_fields = ['locked_by','lock_time']
|
||||||
|
inlines = [ValueListItemInline]
|
||||||
|
search_fields = ['code','name']
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super(ValueListAdmin,self).save_model(request,obj,form,change)
|
||||||
|
obj.valuelistitem_set.update(group_code=obj.code)
|
||||||
|
|
||||||
|
|
||||||
|
class AddressAdmin(generic.BOAdmin):
|
||||||
|
list_display = ['address','phone','contacts']
|
||||||
|
exclude = ['content_type','object_id','creator','modifier','creation','modification','begin','end']
|
||||||
|
|
||||||
|
|
||||||
|
class AddressInline(GenericTabularInline):
|
||||||
|
model = Address
|
||||||
|
exclude = ['content_type','object_id','creator','modifier','creation','modification','begin','end']
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
|
class BankAccountInline(admin.TabularInline):
|
||||||
|
model = BankAccount
|
||||||
|
fields = ['account','title','memo']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerForm(models.ModelForm):
|
||||||
|
tax_address = fields.CharField(widget=TextInput(attrs={'size': 119,}),required=False,label=_("tax address"))
|
||||||
|
memo = fields.CharField(widget=Textarea(attrs={'rows':3,'cols':85}),required=False,label=_("memo"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Partner
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerAdmin(generic.BOAdmin):
|
||||||
|
list_display = ['code','name','partner_type','level']
|
||||||
|
list_display_links = ['code','name']
|
||||||
|
|
||||||
|
fields = (('code','name',),('short','pinyin',),('partner_type','level'),('tax_num','tax_account',),
|
||||||
|
('tax_address',),('contacts','phone',),('memo',),)
|
||||||
|
search_fields = ['code','name','pinyin']
|
||||||
|
form = PartnerForm
|
||||||
|
save_on_top = True
|
||||||
|
inlines = [AddressInline,BankAccountInline]
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
if request.user.is_superuser or (request.user.has_perm('basedate.view_all_customer') and request.user.has_perm('basedate.view_all_supplier')):
|
||||||
|
return super(PartnerAdmin,self).get_queryset(request)
|
||||||
|
elif request.user.has_perm('basedata.view_all_customer'):
|
||||||
|
return super(PartnerAdmin,self).get_queryset(request).filter(partner_type='C')
|
||||||
|
else:
|
||||||
|
return super(PartnerAdmin,self).get_queryset(request).filter(partner_type='S')
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectForm(models.ModelForm):
|
||||||
|
income = fields.DecimalField(required=False,widget=TextInput(attrs={'readonly':'true'}))
|
||||||
|
expand = fields.DecimalField(required=False,widget=TextInput(attrs={'readonly':'true'}))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Project
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'PJ'
|
||||||
|
list_display = ['code','name','status','income','expand']
|
||||||
|
list_display_links = ['code','name']
|
||||||
|
fields = (
|
||||||
|
('code','name',),('short','pinyin',),
|
||||||
|
('partner',),('status','prj_type',),
|
||||||
|
('description',),
|
||||||
|
('budget','income','expand',),('blueprint',),('offer',),('business',),('users',),
|
||||||
|
)
|
||||||
|
search_fields = ['code','name']
|
||||||
|
readonly_fields = ['status']
|
||||||
|
raw_id_fields = ['partner']
|
||||||
|
filter_horizontal = ['users']
|
||||||
|
form = ProjectForm
|
||||||
|
|
||||||
|
|
||||||
|
class WarehouseAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['code','name','location']
|
||||||
|
filter_horizontal = ['users']
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super(WarehouseAdmin,self).save_model(request,obj,form,change)
|
||||||
|
try:
|
||||||
|
code = getattr(obj,'code')
|
||||||
|
if not code:
|
||||||
|
obj.code = '%s%02d' % ('A',obj.id)
|
||||||
|
obj.save()
|
||||||
|
except Exception,e:
|
||||||
|
self.message_user(request,'ERROR:%s' % e,level=messages.ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
class BrandAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['name','pinyin']
|
||||||
|
|
||||||
|
|
||||||
|
class MeasureAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['code','name','status']
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['code','name','path']
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super(CategoryAdmin,self).save_model(request,obj,form,change)
|
||||||
|
try:
|
||||||
|
code = getattr(obj,'code')
|
||||||
|
if not code:
|
||||||
|
obj.code = '%s%02d' % ('F',obj.id)
|
||||||
|
obj.save()
|
||||||
|
if obj.parent:
|
||||||
|
if obj.parent.path:
|
||||||
|
obj.path = obj.parent.path + '/'+obj.parent.name
|
||||||
|
else:
|
||||||
|
obj.path = obj.parent.name
|
||||||
|
obj.save()
|
||||||
|
except Exception,e:
|
||||||
|
self.message_user(request,'ERROR:%s' % e,level=messages.ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
class MaterialForm(models.ModelForm):
|
||||||
|
name = fields.CharField(widget=TextInput(attrs={"size":"119"}),label=_("material name"))
|
||||||
|
spec = fields.CharField(widget=TextInput(attrs={"size":"119"}),required=False,label=_("specifications"))
|
||||||
|
|
||||||
|
class Mata:
|
||||||
|
model = Material
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraParamInline(admin.TabularInline):
|
||||||
|
model = ExtraParam
|
||||||
|
fields = ['name','data_type','data_source']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class MaterialAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'IT'
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
list_display = ['code','name','spec','tp']
|
||||||
|
list_display_links = ['code','name']
|
||||||
|
list_filter = ['brand','tp']
|
||||||
|
search_fields = ['code','name']
|
||||||
|
fields = (
|
||||||
|
('code','barcode'),('name',),('spec',),
|
||||||
|
('brand',),('category',),('status','is_equip','can_sale','is_virtual',),
|
||||||
|
('warehouse',),('tp',),('measure',),('stock_price','purchase_price','sale_price',),
|
||||||
|
)
|
||||||
|
filter_horizontal = ['measure']
|
||||||
|
inlines = [ExtraParamInline]
|
||||||
|
form = MaterialForm
|
||||||
|
|
||||||
|
|
||||||
|
class TechParamValueInline(admin.TabularInline):
|
||||||
|
model = TechnicalParameterValue
|
||||||
|
|
||||||
|
|
||||||
|
class TechParamNameAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['name','category']
|
||||||
|
inlines = [TechParamValueInline]
|
||||||
|
|
||||||
|
|
||||||
|
class TradeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['code','name','parent']
|
||||||
|
|
||||||
|
|
||||||
|
class ExpenseAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'FC'
|
||||||
|
list_display = ['code','name','category']
|
||||||
|
list_display_links = ['code','name']
|
||||||
|
list_filter = ['category']
|
||||||
|
search_fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
|
class FamilyForm(models.ModelForm):
|
||||||
|
name = fields.CharField(widget=TextInput(attrs={"size":"25"}),label=_("name"))
|
||||||
|
phone = fields.CharField(widget=TextInput(attrs={"size":"25"}),label=_("phone"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Family
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class FamilyInline(admin.TabularInline):
|
||||||
|
model = Family
|
||||||
|
exclude = ['creator','modifier','creation','modification','begin','end']
|
||||||
|
form = FamilyForm
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
|
class EducationInline(admin.TabularInline):
|
||||||
|
model = Education
|
||||||
|
exclude = ['creator','modifier','creation','modification']
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class WorkExperienceInline(admin.TabularInline):
|
||||||
|
model = WorkExperience
|
||||||
|
exclude = ['creator','modifier','creation','modification']
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = '1'
|
||||||
|
list_display = ['code','name','position','gender','idcard','age','work_age','literacy','phone','email']
|
||||||
|
search_fields = ['code','name','idcard','pinyin']
|
||||||
|
fieldsets = [
|
||||||
|
(None,{'fields':[('code','phone',),('name','pinyin',),('gender','birthday',),('idcard','country',),
|
||||||
|
('position',),('rank','category'),('status','ygxs',),('workday','startday',)]}),
|
||||||
|
(_('other info'),{'fields':[('hometown','address',),('banknum','bankname',),('email','office',),
|
||||||
|
('emergency','literacy',),('religion','marital',),('party','nation',),('spjob','health',),
|
||||||
|
('major','degree',),('tag1','tag2',),('tag3','tag4',),('user',),],'classes':['collapse']}),
|
||||||
|
]
|
||||||
|
readonly_fields = ['status','ygxs','rank','category']
|
||||||
|
inlines = [FamilyInline,EducationInline,WorkExperienceInline]
|
||||||
|
raw_id_fields = ['user']
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
if request.user.is_superuser or request.user.has_perm('basedata.view_all_employee'):
|
||||||
|
return super(EmployeeAdmin,self).get_queryset(request)
|
||||||
|
else:
|
||||||
|
return super(EmployeeAdmin,self).get_queryset(request).filter(user=request.user)
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return ['status','ygxs','rank','category','position','user']
|
||||||
|
|
||||||
|
|
||||||
|
class DataImportAdmin(generic.BOAdmin):
|
||||||
|
list_display = ['imp_date','title','status']
|
||||||
|
list_display_links = ['imp_date','title']
|
||||||
|
raw_id_fields = ['content_type']
|
||||||
|
readonly_fields = ['status']
|
||||||
|
extra_buttons = [{'href':'action','title':_('import')}]
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
if object_id:
|
||||||
|
obj = DataImport.objects.get(id=object_id)
|
||||||
|
if obj.status == '1':
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
return super(DataImportAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentForm(models.ModelForm):
|
||||||
|
title = fields.CharField(widget=TextInput(attrs={"size":"119"}),label=_("title"))
|
||||||
|
keywords = fields.CharField(widget=TextInput(attrs={"size":"119"}),label=_("keywords"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Document
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'FD'
|
||||||
|
CODE_NUMBER_WIDTH = 4
|
||||||
|
list_display = ['code','title','keywords','tp','business_domain','status','creation']
|
||||||
|
list_display_links = ['code','title']
|
||||||
|
fields = (('code','status',),('title',),('keywords',),('description',),('business_domain','tp',),('attach',))
|
||||||
|
readonly_fields = ['status']
|
||||||
|
list_filter = ['tp','business_domain']
|
||||||
|
search_fields = ['title','keywords','code']
|
||||||
|
form = DocumentForm
|
||||||
|
actions = ['publish']
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
if obj and obj.status=='1':
|
||||||
|
return ['code','status','title','keywords','description','business_domain','tp','attach',]
|
||||||
|
else:
|
||||||
|
return ['status']
|
||||||
|
|
||||||
|
def publish(self,request,queryset):
|
||||||
|
import datetime
|
||||||
|
cnt = queryset.filter(status='0').update(status='1',pub_date=datetime.datetime.now())
|
||||||
|
self.message_user(request,u'%s 个文档发布成功'%cnt)
|
||||||
|
|
||||||
|
publish.short_description = _('publish selected %(verbose_name_plural)s')
|
||||||
|
|
||||||
|
# admin.site.register(Address,AddressAdmin)
|
||||||
|
admin.site.register(ValueList,ValueListAdmin)
|
||||||
|
admin.site.register(Partner,PartnerAdmin)
|
||||||
|
admin.site.register(Project,ProjectAdmin)
|
||||||
|
admin.site.register(Material,MaterialAdmin)
|
||||||
|
admin.site.register(Warehouse,WarehouseAdmin)
|
||||||
|
admin.site.register(Brand,BrandAdmin)
|
||||||
|
admin.site.register(Measure,MeasureAdmin)
|
||||||
|
admin.site.register(Category,CategoryAdmin)
|
||||||
|
admin.site.register(TechnicalParameterName,TechParamNameAdmin)
|
||||||
|
admin.site.register(Trade,TradeAdmin)
|
||||||
|
admin.site.register(ExpenseAccount,ExpenseAdmin)
|
||||||
|
admin.site.register(Employee,EmployeeAdmin)
|
||||||
|
admin.site.register(DataImport,DataImportAdmin)
|
||||||
|
admin.site.register(Document,DocumentAdmin)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
__author__ = 'zhugl'
|
||||||
|
# created at 15-4-22
|
||||||
|
from django.apps.config import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDataConfig(AppConfig):
|
||||||
|
name = 'basedata'
|
||||||
|
verbose_name = _('BaseData')
|
||||||
@@ -0,0 +1,657 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.text import force_text
|
||||||
|
from common import const
|
||||||
|
from common import generic
|
||||||
|
from syscfg.models import Module,Site
|
||||||
|
from organ.models import Organization,Position
|
||||||
|
import datetime
|
||||||
|
from plugin.xls import ExcelManager
|
||||||
|
|
||||||
|
|
||||||
|
class ValueList(generic.BO):
|
||||||
|
"""
|
||||||
|
值列表
|
||||||
|
"""
|
||||||
|
index_weight = 9
|
||||||
|
code = models.CharField(_("list code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||||
|
name = models.CharField(_("list name"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
module = models.ForeignKey(Module,verbose_name=_("module"),blank=True,null=True)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
init = models.BooleanField(_("is init"),default=False)
|
||||||
|
locked = models.BooleanField(_("is locked"),default=False)
|
||||||
|
locked_by = models.ForeignKey(User,verbose_name=_("locked by"),blank=True,null=True)
|
||||||
|
lock_time = models.DateTimeField(_("locked time"),null=True,blank=True)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
super(ValueList,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
sql = 'update basedata_valuelistitem set group_code = %s where groud_id=%s'
|
||||||
|
params = [self.code,self.id]
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('value list')
|
||||||
|
verbose_name_plural = _('value list')
|
||||||
|
|
||||||
|
|
||||||
|
class ValueListItem(models.Model):
|
||||||
|
"""
|
||||||
|
值列表项
|
||||||
|
"""
|
||||||
|
group = models.ForeignKey(ValueList,verbose_name=_("list group"))
|
||||||
|
group_code = models.CharField(max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||||
|
code = models.CharField(_("item code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||||
|
name = models.CharField(_("item name"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
weight = models.IntegerField(_("weight"),null=True,default=9)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if not self.code:
|
||||||
|
cnt = self.group.valuelistitem_set.count()+1
|
||||||
|
self.code = "%02d" % cnt
|
||||||
|
self.group_code = self.group.code
|
||||||
|
super(ValueListItem,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return "%s-%s" % (self.code,self.name)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('list item')
|
||||||
|
verbose_name_plural = _('list item')
|
||||||
|
ordering = ['weight','code']
|
||||||
|
index_together = ['group','group_code']
|
||||||
|
|
||||||
|
|
||||||
|
def get_value_list(group):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param group:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if group:
|
||||||
|
return tuple([(item.code, item.name) for item in ValueListItem.objects.filter(group_code__exact=group,status=1)])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class Address(generic.BO):
|
||||||
|
"""
|
||||||
|
地址
|
||||||
|
"""
|
||||||
|
ADDRESS_TYPE = get_value_list('S011')
|
||||||
|
address_type = models.CharField(_("address type"),max_length=const.DB_CHAR_CODE_2,choices=ADDRESS_TYPE,default='01')
|
||||||
|
address = models.CharField(_("address"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
zipcode = models.CharField(_("zipcode"),max_length=const.DB_CHAR_CODE_8,blank=True,null=True)
|
||||||
|
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
contacts = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
|
||||||
|
content_type = models.ForeignKey(ContentType,blank=True,null=True)
|
||||||
|
object_id = models.PositiveIntegerField(blank=True,null=True)
|
||||||
|
content_object = GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('address')
|
||||||
|
verbose_name_plural = _('address')
|
||||||
|
|
||||||
|
|
||||||
|
class Partner(generic.BO):
|
||||||
|
"""
|
||||||
|
合作伙伴
|
||||||
|
"""
|
||||||
|
index_weight = 3
|
||||||
|
PARTNER_TYPE = (
|
||||||
|
('C', _('Customer')),
|
||||||
|
('S', _('Supplier')),
|
||||||
|
)
|
||||||
|
|
||||||
|
LEVEL = (
|
||||||
|
('A','A'),
|
||||||
|
('B','B'),
|
||||||
|
('C','C'),
|
||||||
|
('D','D'),
|
||||||
|
)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
code = models.CharField(_("partner code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
name = models.CharField(_("partner name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
partner_type = models.CharField(_("type"),max_length=const.DB_CHAR_CODE_2,choices=PARTNER_TYPE,default='C')
|
||||||
|
level = models.CharField(_("level"),max_length=const.DB_CHAR_CODE_2,choices=LEVEL,default='C')
|
||||||
|
|
||||||
|
tax_num = models.CharField(_("tax num"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
tax_address = models.CharField(_("tax address"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
tax_account = models.CharField(_("tax account"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||||
|
|
||||||
|
contacts = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
memo = models.TextField(_("memo"),blank=True,null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('partner')
|
||||||
|
verbose_name_plural = _('partner')
|
||||||
|
permissions = (
|
||||||
|
('view_all_customer',_("view all customer")),
|
||||||
|
('view_all_supplier',_("view all supplier")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BankAccount(generic.BO):
|
||||||
|
"""
|
||||||
|
银行账户 organization
|
||||||
|
"""
|
||||||
|
account = models.CharField(_("account num"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
title = models.CharField(_("bank name"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
memo = models.CharField(_("memo"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
partner = models.ForeignKey(Partner,verbose_name=_("partner"),blank=True,null=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
name = ''
|
||||||
|
if self.org:
|
||||||
|
name = self.org.name
|
||||||
|
elif self.partner:
|
||||||
|
name = self.partner.name
|
||||||
|
return u"%s %s %s" % (name,self.account,self.title)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('bank account')
|
||||||
|
verbose_name_plural = _('bank account')
|
||||||
|
|
||||||
|
|
||||||
|
class Project(generic.BO):
|
||||||
|
"""
|
||||||
|
工程项目
|
||||||
|
"""
|
||||||
|
STATUS = get_value_list('S012')
|
||||||
|
TYPES = get_value_list('S013')
|
||||||
|
index_weight = 1
|
||||||
|
|
||||||
|
code = models.CharField(_("project code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
name = models.CharField(_("project name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
|
||||||
|
partner = models.ForeignKey(Partner,blank=True,null=True,verbose_name=_("partner"),limit_choices_to={"partner_type":"C"})
|
||||||
|
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,default='00',choices=STATUS)
|
||||||
|
prj_type = models.CharField(_("project type"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=TYPES,default='00')
|
||||||
|
|
||||||
|
description = models.TextField(_("description"),blank=True,null=True)
|
||||||
|
|
||||||
|
budget = models.DecimalField(_("budget"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||||
|
income = models.DecimalField(_("income"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||||
|
expand = models.DecimalField(_("expand"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||||
|
|
||||||
|
blueprint = models.FileField(_("blueprint"),upload_to='project',blank=True,null=True)
|
||||||
|
offer = models.FileField(_("offer sheet"),upload_to='offer sheet',blank=True,null=True)
|
||||||
|
business = models.FileField(_("business document"),upload_to='project',blank=True,null=True)
|
||||||
|
|
||||||
|
users = models.ManyToManyField(User,verbose_name=_("related users"),blank=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('project')
|
||||||
|
verbose_name_plural = _('project')
|
||||||
|
|
||||||
|
|
||||||
|
class Warehouse(models.Model):
|
||||||
|
"""
|
||||||
|
仓库
|
||||||
|
"""
|
||||||
|
index_weight = 6
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
location = models.CharField(_("location"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
users = models.ManyToManyField(User,verbose_name=_("related users"),blank=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s' % self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('warehouse')
|
||||||
|
verbose_name_plural = _('warehouse')
|
||||||
|
|
||||||
|
|
||||||
|
class Measure(models.Model):
|
||||||
|
"""
|
||||||
|
计量单位
|
||||||
|
"""
|
||||||
|
index_weight = 5
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_20)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s' % self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('measure')
|
||||||
|
verbose_name_plural = _('measure')
|
||||||
|
|
||||||
|
|
||||||
|
class Trade(models.Model):
|
||||||
|
"""
|
||||||
|
国民经济行业分类
|
||||||
|
"""
|
||||||
|
index_weight = 102
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_6)
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
memo = models.CharField(_("memo"),max_length=const.DB_CHAR_NAME_120,null=True,blank=True)
|
||||||
|
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s' % self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('trade')
|
||||||
|
verbose_name_plural = _('trade')
|
||||||
|
ordering = ['code']
|
||||||
|
|
||||||
|
|
||||||
|
class Brand(models.Model):
|
||||||
|
"""
|
||||||
|
品牌
|
||||||
|
"""
|
||||||
|
index_weight = 101
|
||||||
|
trade = models.ForeignKey(Trade,verbose_name=_("trade"),null=True,blank=True)
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
weight = models.IntegerField(_("weight"),blank=True,null=True,default=99)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s' % self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('brand')
|
||||||
|
verbose_name_plural = _('brand')
|
||||||
|
|
||||||
|
|
||||||
|
class Category(models.Model):
|
||||||
|
"""
|
||||||
|
分类
|
||||||
|
"""
|
||||||
|
index_weight = 100
|
||||||
|
trade = models.ForeignKey(Trade,verbose_name=_("trade"),null=True,blank=True)
|
||||||
|
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_6,null=True,blank=True)
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
path = models.CharField(_("path"),max_length=const.DB_CHAR_NAME_200,null=True,blank=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s' % self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('category')
|
||||||
|
verbose_name_plural = _('category')
|
||||||
|
|
||||||
|
|
||||||
|
class TechnicalParameterName(models.Model):
|
||||||
|
"""
|
||||||
|
技术参数-名称,将技术参数绑定于物料分类上,在此分类下的物料自动继承全部技术参数
|
||||||
|
"""
|
||||||
|
index_weight = 7
|
||||||
|
category = models.ForeignKey(Category,verbose_name=_("material category"))
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s' % self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('technical parameter')
|
||||||
|
verbose_name_plural = _('technical parameter')
|
||||||
|
|
||||||
|
|
||||||
|
class TechnicalParameterValue(models.Model):
|
||||||
|
"""
|
||||||
|
技术参数-值,将技术参数绑定于物料分类上,在此分类下的物料自动继承全部技术参数
|
||||||
|
"""
|
||||||
|
tech_name = models.ForeignKey(TechnicalParameterName,verbose_name=_("technical name"))
|
||||||
|
value = models.CharField(_("value"),max_length=const.DB_CHAR_NAME_80)
|
||||||
|
description = models.CharField(_("description"),max_length=const.DB_CHAR_NAME_80,null=True,blank=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s' % self.value
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('technical value')
|
||||||
|
verbose_name_plural = _('technical value')
|
||||||
|
|
||||||
|
|
||||||
|
class Material(generic.BO):
|
||||||
|
"""
|
||||||
|
物料
|
||||||
|
"""
|
||||||
|
index_weight = 4
|
||||||
|
code = models.CharField(_("material code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
barcode = models.CharField(_("bar code"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
name = models.CharField(_("material name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
spec = models.CharField(_("specifications"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
brand = models.ForeignKey(Brand,blank=True,null=True,verbose_name=_("brand"))
|
||||||
|
category = models.ForeignKey(Category,blank=True,null=True,verbose_name=_("category"))
|
||||||
|
tp = models.CharField(_('mt type'),blank=True,null=True,max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S054'),default='10')
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
is_equip = models.BooleanField(_("is equipment"),default=False)
|
||||||
|
can_sale = models.BooleanField(_("can sale"),default=True)
|
||||||
|
is_virtual = models.BooleanField(_("is virtual"),default=False)
|
||||||
|
|
||||||
|
warehouse = models.ForeignKey(Warehouse,blank=True,null=True,verbose_name=_("warehouse"))
|
||||||
|
measure = models.ManyToManyField(Measure,verbose_name=_("measure"))
|
||||||
|
|
||||||
|
params = models.ManyToManyField(TechnicalParameterValue,verbose_name=_("technical parameter"),through='MaterialParam')
|
||||||
|
|
||||||
|
stock_price = models.DecimalField(_("stock price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
purchase_price = models.DecimalField(_("purchase price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
sale_price = models.DecimalField(_("sale price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
|
||||||
|
return "%s %s" % (self.code,self.name)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('material')
|
||||||
|
verbose_name_plural = _('material')
|
||||||
|
ordering = ['tp','code']
|
||||||
|
|
||||||
|
|
||||||
|
class MaterialParam(models.Model):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
material = models.ForeignKey(Material)
|
||||||
|
param_value = models.ForeignKey(TechnicalParameterValue)
|
||||||
|
param_name = models.ForeignKey(TechnicalParameterName,blank=Trade,null=True)
|
||||||
|
creation = models.DateField(auto_now_add=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s' % self.param_value
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('material parameter')
|
||||||
|
verbose_name_plural = _('material parameter')
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraParam(models.Model):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
DATA_TYPE = (
|
||||||
|
('CHAR',_('CHAR')),
|
||||||
|
('NUM',_('NUMBER')),
|
||||||
|
('DATE',_('DATE')),
|
||||||
|
)
|
||||||
|
material = models.ForeignKey(Material,verbose_name=_("material"))
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
data_type = models.CharField(_("data type"),default='CHAR',choices=DATA_TYPE,max_length=const.DB_CHAR_CODE_6)
|
||||||
|
data_source = models.CharField(_("data source"),blank=True,null=True,max_length=const.DB_CHAR_NAME_40)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return "%s" % self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("extra param")
|
||||||
|
verbose_name_plural = _("extra params")
|
||||||
|
|
||||||
|
|
||||||
|
class ExpenseAccount(generic.BO):
|
||||||
|
"""
|
||||||
|
费用科目
|
||||||
|
"""
|
||||||
|
CATEGORY = (
|
||||||
|
('HR',_('HR-DOMAIN')),
|
||||||
|
('OF',_('OFFICE-DOMAIN')),
|
||||||
|
('PU',_('PUBLIS-DOMAIN')),
|
||||||
|
('MU',_('MUNADOMAIN')),
|
||||||
|
('BU',_('BUSINESS')),
|
||||||
|
('OT',_('OTHER')),
|
||||||
|
)
|
||||||
|
index_weight = 10
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
category = models.CharField(_("category"),max_length=const.DB_CHAR_CODE_4,choices=CATEGORY,default='PU')
|
||||||
|
description = models.TextField(_("description"),blank=True,null=True)
|
||||||
|
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('expenses account')
|
||||||
|
verbose_name_plural = _('expenses account')
|
||||||
|
ordering = ['category','code']
|
||||||
|
|
||||||
|
|
||||||
|
class Employee(generic.BO):
|
||||||
|
"""
|
||||||
|
职员信息
|
||||||
|
"""
|
||||||
|
index_weight = 2
|
||||||
|
code = models.CharField(_("employee number"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
organization = models.ForeignKey(Organization,verbose_name = _('organization'),null=True,blank=True)
|
||||||
|
name = models.CharField(_("employee name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
birthday = models.DateField(_("birthday"),blank=True,null=True)
|
||||||
|
|
||||||
|
gender = models.CharField(_("gender"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('gender'),default='1')
|
||||||
|
idcard = models.CharField(_("id card"),max_length=const.DB_CHAR_NAME_20)
|
||||||
|
|
||||||
|
country = models.CharField(_("nationality"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,default='CN',choices=const.get_value_list('S022'))
|
||||||
|
hometown = models.CharField(_("hometown"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
address = models.CharField(_("home address"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
banknum = models.CharField(_("bank account"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
bankname = models.CharField(_("bank name"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||||
|
emergency = models.CharField(_("emergency contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
email = models.CharField(_("email"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
office = models.CharField(_("office phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
|
||||||
|
position = models.ForeignKey(Position,verbose_name = _('position'))
|
||||||
|
rank = models.CharField(_("employee rank"),max_length=const.DB_CHAR_CODE_2,default='00',choices=const.get_value_list('S017'))
|
||||||
|
|
||||||
|
workday = models.DateField(_("workday"),blank=True,null=True)
|
||||||
|
startday = models.DateField(_("start date"),blank=True,null=True)
|
||||||
|
|
||||||
|
religion = models.CharField(_("religion"),max_length=const.DB_CHAR_CODE_2,default='00',choices=const.get_value_list('S020'),blank=True,null=True,)
|
||||||
|
marital = models.CharField(_("marital status"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S023'),default='10')
|
||||||
|
|
||||||
|
party = models.CharField(_("political party"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S026'),default='13')
|
||||||
|
nation = models.CharField(_("nation"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S021'),default='01')
|
||||||
|
|
||||||
|
ygxs = models.CharField(_("employ ygxs"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S019'),default='2')
|
||||||
|
status = models.CharField(_("employ status"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S016'),default='10')
|
||||||
|
category = models.CharField(_("employ category"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S018'),default='21')
|
||||||
|
|
||||||
|
literacy = models.CharField(_("literacy"),max_length=const.DB_CHAR_CODE_2,default='10',choices=const.get_value_list('S024'),blank=True,null=True)
|
||||||
|
major = models.CharField(_("major type"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S038'),default='99')
|
||||||
|
degree = models.CharField(_("major degree"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S037'),default='4')
|
||||||
|
|
||||||
|
spjob = models.CharField(_("special job"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S042'),default='00')
|
||||||
|
health = models.CharField(_("health"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S043'),default='1')
|
||||||
|
|
||||||
|
tag1 = models.CharField(_("tag1 fzjr"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S039'),default='99')
|
||||||
|
tag2 = models.CharField(_("tag2 dwld"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S040'),default='9')
|
||||||
|
tag3 = models.CharField(_("tag3 dsjs"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S041'),default='00')
|
||||||
|
tag4 = models.CharField(_("tag4 byzk"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S027'),default='0')
|
||||||
|
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||||
|
|
||||||
|
def age(self):
|
||||||
|
import datetime
|
||||||
|
if self.birthday:
|
||||||
|
cnt = datetime.date.today().year-self.birthday.year
|
||||||
|
return cnt
|
||||||
|
|
||||||
|
def work_age(self):
|
||||||
|
import datetime
|
||||||
|
if self.birthday and self.workday:
|
||||||
|
cnt = datetime.date.today().year-self.workday.year
|
||||||
|
return cnt
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'%s %s'%(self.code,self.name)
|
||||||
|
|
||||||
|
age.short_description = u'年龄'
|
||||||
|
work_age.short_description = u'工龄'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("employee")
|
||||||
|
verbose_name_plural = _("employee")
|
||||||
|
permissions = (
|
||||||
|
('view_all_employee',_("view all employee")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Family(generic.BO):
|
||||||
|
"""
|
||||||
|
家庭成员
|
||||||
|
"""
|
||||||
|
relation = models.CharField(_("family title"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S025'))
|
||||||
|
status = models.CharField(_("social status"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S029'),default='17')
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_60)
|
||||||
|
birthday = models.DateField(_("birthday"),blank=True,null=True)
|
||||||
|
organization = models.CharField(_("organization"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
emergency = models.BooleanField(_("emergency"),default=False)
|
||||||
|
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("family member")
|
||||||
|
verbose_name_plural = _("family member")
|
||||||
|
|
||||||
|
|
||||||
|
class Education(generic.BO):
|
||||||
|
"""
|
||||||
|
教育履历
|
||||||
|
"""
|
||||||
|
edu_type = models.CharField(_("edu type"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S035'),default='1')
|
||||||
|
school = models.CharField(_("school"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
major = models.CharField(_("major"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
degree = models.CharField(_("major degree"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S037'),default='4')
|
||||||
|
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("education experience")
|
||||||
|
verbose_name_plural = _("education experience")
|
||||||
|
|
||||||
|
|
||||||
|
class WorkExperience(generic.BO):
|
||||||
|
"""
|
||||||
|
工作履历
|
||||||
|
"""
|
||||||
|
organization = models.CharField(_("organization"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
position = models.CharField(_("position"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("work experience")
|
||||||
|
verbose_name_plural = _("work experience")
|
||||||
|
|
||||||
|
|
||||||
|
class DataImport(generic.BO):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Data import
|
||||||
|
"""
|
||||||
|
actions = {}
|
||||||
|
|
||||||
|
STATUS = (
|
||||||
|
('0',_('NEW')),
|
||||||
|
('1',_('EXECUTED')),
|
||||||
|
)
|
||||||
|
imp_date = models.DateField(_('date'),blank=True,null=True,default=datetime.datetime.today)
|
||||||
|
title = models.CharField(_('title'),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
description = models.TextField(_('description'),blank=True,null=True)
|
||||||
|
content_type = models.ForeignKey(ContentType,verbose_name=_("content type"),limit_choices_to={"app_label__in":['basedata','organ','auth']})
|
||||||
|
attach = models.FileField(_('attach'),blank=True,null=True,upload_to='data')
|
||||||
|
is_clear = models.BooleanField(_('clear old data?'),default=0)
|
||||||
|
handler = models.CharField(_('handler class'),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||||
|
status = models.CharField(_('status'),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||||
|
|
||||||
|
def action_import(self,request):
|
||||||
|
from django.db import transaction
|
||||||
|
if self.attach:
|
||||||
|
if self.handler:
|
||||||
|
klass = ExcelManager().handlers.get(self.handler)
|
||||||
|
with transaction.atomic():
|
||||||
|
klass.handle(self,self.attach)
|
||||||
|
self.status = 1
|
||||||
|
self.save()
|
||||||
|
else:
|
||||||
|
import xlrd
|
||||||
|
import os
|
||||||
|
from mis import settings
|
||||||
|
path = os.path.join(settings.MEDIA_ROOT,self.attach.name)
|
||||||
|
workbook = xlrd.open_workbook(path)
|
||||||
|
sheet = workbook.sheet_by_index(0)
|
||||||
|
row_count = sheet.nrows
|
||||||
|
col_count = sheet.ncols
|
||||||
|
cols = []
|
||||||
|
with transaction.atomic():
|
||||||
|
for row_index in range(row_count):
|
||||||
|
line = sheet.row_values(row_index)
|
||||||
|
if row_index == 0:
|
||||||
|
cols = line
|
||||||
|
continue
|
||||||
|
elif row_index == 1:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
klass = self.content_type.model_class()
|
||||||
|
values = line
|
||||||
|
params = {}
|
||||||
|
for name in cols:
|
||||||
|
index = cols.index(name)
|
||||||
|
v = values[index]
|
||||||
|
if type(v) == str:
|
||||||
|
v = force_text(v.decode('gbk'))
|
||||||
|
params[name]=v
|
||||||
|
# print 'name is %s value is %s'%(name,v)
|
||||||
|
try:
|
||||||
|
params.pop('')
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
# print params
|
||||||
|
klass.objects.create(**params)
|
||||||
|
self.status = '1'
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("data import")
|
||||||
|
verbose_name_plural = _("data import")
|
||||||
|
|
||||||
|
|
||||||
|
class Document(generic.BO):
|
||||||
|
"""
|
||||||
|
文档管理
|
||||||
|
"""
|
||||||
|
TP = (
|
||||||
|
('00',_('SYSTEM MANUAL')),
|
||||||
|
('10',_('BUSINESS DOC')),
|
||||||
|
)
|
||||||
|
STATUS = (
|
||||||
|
('0',_('draft')),
|
||||||
|
('1',_('published'))
|
||||||
|
)
|
||||||
|
index_weight = 8
|
||||||
|
code = models.CharField(_('code'),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
title = models.CharField(_('title'),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
keywords = models.CharField(_('keywords'),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
description = models.TextField(_('description'),blank=True,null=True)
|
||||||
|
tp = models.CharField(_('type'),max_length=const.DB_CHAR_CODE_2,default='10',choices=TP)
|
||||||
|
business_domain = models.CharField(_("business domain"),max_length=const.DB_CHAR_CODE_4,choices=const.get_value_list('S045'),default='OT')
|
||||||
|
user = models.ForeignKey(User,verbose_name=_('user'),blank=True,null=True)
|
||||||
|
status = models.CharField(_('status'),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||||
|
pub_date = models.DateTimeField(_('publish date'),blank=True,null=True)
|
||||||
|
size = models.CharField(_('size'),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
attach = models.FileField(_('attach'),blank=True,null=True,upload_to='doc')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("document")
|
||||||
|
verbose_name_plural = _("documents")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from django.conf.urls import include, url,static
|
||||||
|
import basedata.views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r"dataimport/(?P<object_id>\d+)/action", basedata.views.action_import),
|
||||||
|
]
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.contrib.admin import site
|
||||||
|
from django.http.response import HttpResponseRedirect
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.contrib import messages
|
||||||
|
from basedata.models import DataImport
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
def action_import(request,object_id):
|
||||||
|
"""
|
||||||
|
数据导入操作
|
||||||
|
:param request:
|
||||||
|
:param object_id:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
title = _("Are you sure?")
|
||||||
|
obj = DataImport.objects.get(id=int(object_id))
|
||||||
|
opts = obj._meta
|
||||||
|
objects_name = force_text(opts.verbose_name)
|
||||||
|
|
||||||
|
if request.POST.get("post"):
|
||||||
|
obj.action_import(request)
|
||||||
|
try:
|
||||||
|
|
||||||
|
messages.success(request,_('data import successfully'))
|
||||||
|
except Exception,e:
|
||||||
|
messages.error(request,e)
|
||||||
|
|
||||||
|
return HttpResponseRedirect("/admin/basedata/dataimport/%s"%(object_id))
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
opts=opts,
|
||||||
|
objects_name=objects_name,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
request.current_app = site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__author__ = 'Administrator'
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.db import connection
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
|
||||||
|
DB_CHAR_CODE_2 = 2
|
||||||
|
DB_CHAR_CODE_4 = 4
|
||||||
|
DB_CHAR_CODE_6 = 6
|
||||||
|
DB_CHAR_CODE_8 = 8
|
||||||
|
DB_CHAR_CODE_10 = 10
|
||||||
|
|
||||||
|
DB_CHAR_NAME_20 = 20
|
||||||
|
DB_CHAR_NAME_40 = 40
|
||||||
|
DB_CHAR_NAME_60 = 60
|
||||||
|
DB_CHAR_NAME_80 = 80
|
||||||
|
DB_CHAR_NAME_120 = 120
|
||||||
|
DB_CHAR_NAME_200 = 200
|
||||||
|
|
||||||
|
|
||||||
|
STATUS_ON_OFF = (
|
||||||
|
(0,_('OFF')),
|
||||||
|
(0,_('ON')),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_value_list(group):
|
||||||
|
"""
|
||||||
|
获取值列表信息
|
||||||
|
"""
|
||||||
|
if group:
|
||||||
|
try:
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute('SELECT code,name FROM basedata_valuelistitem WHERE group_code=%s AND status=1',[group])
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
return tuple([(code,name) for code,name in rows])
|
||||||
|
except Exception,e:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
# created at 15-4-21
|
||||||
|
import datetime
|
||||||
|
import xlwt
|
||||||
|
import re
|
||||||
|
from django.db import models
|
||||||
|
from django.db import connection,transaction
|
||||||
|
from django.db.models import fields
|
||||||
|
from django.db.models.fields import related
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.http import HttpRequest,HttpResponseRedirect,HttpResponse
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils.text import force_text
|
||||||
|
from django.utils.encoding import smart_str
|
||||||
|
from django.utils.http import urlquote
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import const
|
||||||
|
from midware import cuser
|
||||||
|
|
||||||
|
|
||||||
|
def update(sql, params=None):
|
||||||
|
"""
|
||||||
|
:param sql:
|
||||||
|
:param params:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
cursor = connection.cursor()
|
||||||
|
with transaction.atomic():
|
||||||
|
try:
|
||||||
|
if params:
|
||||||
|
cursor.execute(sql,params)
|
||||||
|
else:
|
||||||
|
cursor.execute(sql)
|
||||||
|
except Exception,e:
|
||||||
|
print e
|
||||||
|
|
||||||
|
def get_app_model_info_from_request(request):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
if request and isinstance(request,HttpRequest):
|
||||||
|
import re
|
||||||
|
pattern = re.compile(r"/(admin)/(\w+)/(\w+)/(\d+)")
|
||||||
|
match = pattern.match(request.path)
|
||||||
|
|
||||||
|
if match and match.group():
|
||||||
|
app = match.group(2)
|
||||||
|
model = match.group(3)
|
||||||
|
oid = match.group(4)
|
||||||
|
ct = ContentType.objects.get(app_label=app,model=model)
|
||||||
|
obj = ct.get_object_for_this_type(id=oid)
|
||||||
|
return {'app':app,'model':model,'id':oid,'obj':obj}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class MineBOManager(models.Manager):
|
||||||
|
"""
|
||||||
|
get the objects created by current login user
|
||||||
|
"""
|
||||||
|
def get_query_set(self):
|
||||||
|
return super(MineBOManager,self).get_query_set().filter(creator=cuser.getuser())
|
||||||
|
|
||||||
|
|
||||||
|
class BOManager(models.Manager):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
def get_query_set(self):
|
||||||
|
return super(BOManager,self).get_query_set()
|
||||||
|
|
||||||
|
|
||||||
|
class BO(models.Model):
|
||||||
|
"""
|
||||||
|
All business object derive from this class
|
||||||
|
"""
|
||||||
|
begin = models.DateField(_('begin date'),blank=True,null=True)
|
||||||
|
end = models.DateField(_('end date'),blank=True,null=True)
|
||||||
|
creator = models.CharField(_("creator"),blank=True,null=True,max_length=const.DB_CHAR_NAME_20)
|
||||||
|
modifier = models.CharField(_("modifier"),blank=True,null=True,max_length=const.DB_CHAR_NAME_20)
|
||||||
|
creation = models.DateTimeField(_('creation'),auto_now_add=True,blank=True,null=True)
|
||||||
|
modification = models.DateTimeField(_('modification'),auto_now=True,blank=True,null=True)
|
||||||
|
# mine = MineBOManager()
|
||||||
|
objects = models.Manager()
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
display = getattr(self,'name',None) or getattr(self,'title',None) or getattr(self,'description',None)
|
||||||
|
if not display:
|
||||||
|
display = ' '
|
||||||
|
return u'%s' % display
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class BOAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
All business object admin derive from this class
|
||||||
|
"""
|
||||||
|
CODE_NUMBER_WIDTH = 4
|
||||||
|
CODE_PREFIX = '9'
|
||||||
|
extra_buttons = []
|
||||||
|
|
||||||
|
exclude = ['creator','modifier','creation','modification','begin','end']
|
||||||
|
list_per_page = 18
|
||||||
|
actions = ['export_selected_data']
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param request:
|
||||||
|
:param object_id:
|
||||||
|
:param form_url:
|
||||||
|
:param extra_context:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
app_info = get_app_model_info_from_request(request)
|
||||||
|
workflow_modal = None
|
||||||
|
workflow_instance = None
|
||||||
|
has_workflow_modal = False
|
||||||
|
has_workflow_instance = False
|
||||||
|
show_workflow_line = False
|
||||||
|
show_submit_button = False
|
||||||
|
can_restart = False
|
||||||
|
can_edit = False
|
||||||
|
# print app_info
|
||||||
|
if app_info:
|
||||||
|
try:
|
||||||
|
modal = ContentType.objects.get(app_label='workflow',model='modal')
|
||||||
|
workflow_modal = modal.get_object_for_this_type(app_name=app_info['app'],model_name=app_info['model'])
|
||||||
|
has_workflow_modal = True
|
||||||
|
# print workflow_modal.code
|
||||||
|
instance = ContentType.objects.get(app_label='workflow',model='instance')
|
||||||
|
workflow_instance = instance.get_object_for_this_type(modal=workflow_modal,object_id=app_info['id'])
|
||||||
|
has_workflow_instance = True
|
||||||
|
todo = ContentType.objects.get(app_label='workflow',model='todolist')
|
||||||
|
todo_list = todo.model_class().objects.filter(inst=workflow_instance,status=0,user=request.user)
|
||||||
|
x = todo_list.all()
|
||||||
|
|
||||||
|
if x and len(x)>0:
|
||||||
|
can_edit = x[0].node.can_edit
|
||||||
|
if todo_list.count() > 0:
|
||||||
|
# print 'we fount it'
|
||||||
|
unread = todo_list.filter(is_read=0)
|
||||||
|
show_workflow_line = True
|
||||||
|
if unread.count() > 0:
|
||||||
|
unread.update(is_read=1,read_time=datetime.datetime.now())
|
||||||
|
if workflow_instance.status == 3 and request.user == workflow_instance.starter:
|
||||||
|
can_restart = True
|
||||||
|
show_workflow_line = True
|
||||||
|
|
||||||
|
except Exception,e:
|
||||||
|
print Exception,e
|
||||||
|
|
||||||
|
if workflow_modal and not workflow_instance:
|
||||||
|
show_submit_button = True
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
ctx = dict(
|
||||||
|
has_workflow_instance = has_workflow_instance,
|
||||||
|
has_workflow_modal = has_workflow_modal,
|
||||||
|
workflow_modal = workflow_modal,
|
||||||
|
workflow_instance = workflow_instance,
|
||||||
|
show_workflow_line = show_workflow_line,
|
||||||
|
can_restart = can_restart,
|
||||||
|
can_edit = can_edit,
|
||||||
|
show_submit_button = show_submit_button,
|
||||||
|
)
|
||||||
|
if len(self.extra_buttons) > 0:
|
||||||
|
buttons = dict(
|
||||||
|
extra_buttons = self.extra_buttons
|
||||||
|
)
|
||||||
|
ctx.update(buttons)
|
||||||
|
extra_context.update(ctx)
|
||||||
|
# print extra_context
|
||||||
|
return super(BOAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
def history_view(self, request, object_id, extra_context=None):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param request:
|
||||||
|
:param object_id:
|
||||||
|
:param extra_context:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
app_info = get_app_model_info_from_request(request)
|
||||||
|
# print app_info
|
||||||
|
if app_info:
|
||||||
|
try:
|
||||||
|
modal = ContentType.objects.get(app_label='workflow',model='modal')
|
||||||
|
workflow_modal = modal.get_object_for_this_type(app_name=app_info['app'],model_name=app_info['model'])
|
||||||
|
has_workflow_modal = True
|
||||||
|
instance = ContentType.objects.get(app_label='workflow',model='instance')
|
||||||
|
workflow_instance = instance.get_object_for_this_type(modal=workflow_modal,object_id=app_info['id'])
|
||||||
|
has_workflow_instance = True
|
||||||
|
history = ContentType.objects.get(app_label='workflow',model='history')
|
||||||
|
history_list = history.model_class().objects.filter(inst=workflow_instance)
|
||||||
|
has_history = True
|
||||||
|
todo = ContentType.objects.get(app_label='workflow',model='todolist')
|
||||||
|
todo_list = todo.model_class().objects.filter(inst=workflow_instance,status=0).exclude(node=None)
|
||||||
|
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
ctx = dict(
|
||||||
|
has_workflow_instance = has_workflow_instance,
|
||||||
|
has_workflow_modal = has_workflow_modal,
|
||||||
|
workflow_modal = workflow_modal,
|
||||||
|
workflow_instance = workflow_instance,
|
||||||
|
history_list = history_list,
|
||||||
|
has_history = has_history,
|
||||||
|
todo_list = todo_list,
|
||||||
|
)
|
||||||
|
# print history_list
|
||||||
|
extra_context.update(ctx)
|
||||||
|
except Exception,e:
|
||||||
|
print Exception,e
|
||||||
|
pass
|
||||||
|
return super(BOAdmin,self).history_view(request,object_id,extra_context)
|
||||||
|
|
||||||
|
def get_changeform_initial_data(self, request):
|
||||||
|
import datetime
|
||||||
|
return {'begin':datetime.date.today,'end':datetime.date(9999,12,31)}
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
|
||||||
|
if change:
|
||||||
|
setattr(obj,'modifier',request.user.username)
|
||||||
|
else:
|
||||||
|
setattr(obj,'creator',request.user.username)
|
||||||
|
setattr(obj,'begin',datetime.date.today())
|
||||||
|
setattr(obj,'end',datetime.date(9999,12,31))
|
||||||
|
try:
|
||||||
|
setattr(obj,'user',request.user)
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
super(BOAdmin,self).save_model(request,obj,form,change)
|
||||||
|
# print '=========it is here========='
|
||||||
|
try:
|
||||||
|
code = getattr(obj,'code')
|
||||||
|
# print code
|
||||||
|
if code is None or len(code) == 0:
|
||||||
|
fmt = '%s%0'+str(self.CODE_NUMBER_WIDTH)+'d'
|
||||||
|
code = fmt % (self.CODE_PREFIX,obj.id)
|
||||||
|
table = obj._meta.db_table
|
||||||
|
sql = 'update %s set code = \'%s\' where id=%s' % (table,code,obj.id)
|
||||||
|
print sql
|
||||||
|
update(sql)
|
||||||
|
except Exception,e:
|
||||||
|
print e
|
||||||
|
|
||||||
|
# def response_change(self, request, obj):
|
||||||
|
# return HttpResponseRedirect('')
|
||||||
|
|
||||||
|
def export_selected_data(self,request,queryset):
|
||||||
|
ops = self.model._meta
|
||||||
|
workbook = xlwt.Workbook(encoding='utf-8')
|
||||||
|
dd = datetime.date.today().strftime('%Y%m%d')
|
||||||
|
file_name = force_text(ops.verbose_name+dd)
|
||||||
|
sheet = workbook.add_sheet(force_text(ops.verbose_name))
|
||||||
|
obj_fields = getattr(self,'export_fields',None) or self.list_display or self.fields
|
||||||
|
|
||||||
|
head_col_index = 0
|
||||||
|
for field in obj_fields:
|
||||||
|
col_name = field
|
||||||
|
try:
|
||||||
|
f = ops.get_field(field)
|
||||||
|
col_name = f.verbose_name
|
||||||
|
except Exception,e:
|
||||||
|
f = getattr(self.model,field)
|
||||||
|
if hasattr(f,'short_description'):
|
||||||
|
col_name = f.short_description
|
||||||
|
sheet.write(0,head_col_index,force_text(col_name))
|
||||||
|
head_col_index+=1
|
||||||
|
row_index = 1
|
||||||
|
for obj in queryset:
|
||||||
|
col_index = 0
|
||||||
|
for field in obj_fields:
|
||||||
|
f = field
|
||||||
|
try:
|
||||||
|
f = ops.get_field(field)
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
v = getattr(obj,field,'')
|
||||||
|
if hasattr(v,'__call__') or callable(v):
|
||||||
|
v = v()
|
||||||
|
elif type(f) == fields.DateField:
|
||||||
|
v = v.strftime('%Y-%m-%d')
|
||||||
|
elif type(f) == fields.DateTimeField:
|
||||||
|
v = v.strftime('%Y-%m-%d %H:%M')
|
||||||
|
elif type(f) == fields.CharField and f.choices:
|
||||||
|
fc = 'get_'+field+'_display'
|
||||||
|
v = getattr(obj,fc)()
|
||||||
|
elif type(f) == related.ForeignKey:
|
||||||
|
v = str(v)
|
||||||
|
sheet.write(row_index,col_index,v)
|
||||||
|
col_index += 1
|
||||||
|
row_index += 1
|
||||||
|
response = HttpResponse(content_type='application/vnd.ms-excel')
|
||||||
|
agent = request.META.get('HTTP_USER_AGENT')
|
||||||
|
nn = smart_str(file_name)
|
||||||
|
if agent and re.search('MSIE',agent):
|
||||||
|
nn = urlquote(file_name)
|
||||||
|
response['Content-Disposition'] = 'attachment; filename=%s.xls'%nn
|
||||||
|
workbook.save(response)
|
||||||
|
return response
|
||||||
|
#self.message_user(request,'SUCCESS')
|
||||||
|
export_selected_data.short_description = _("export selected %(verbose_name_plural)s")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-creation']
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
css = {'all':('css/maximus.css',)}
|
||||||
|
js = ('js/maximus.js',)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
ports:
|
||||||
|
- "6010:8000"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
environment:
|
||||||
|
- DATABASE_HOST=db
|
||||||
|
- DATABASE_NAME=mis
|
||||||
|
- DATABASE_USER=root
|
||||||
|
- DATABASE_PASSWORD=root
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: mysql:5.7
|
||||||
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
- ./Install/SQL:/docker-entrypoint-initdb.d
|
||||||
|
environment:
|
||||||
|
- MYSQL_DATABASE=mis
|
||||||
|
- MYSQL_ROOT_PASSWORD=root
|
||||||
|
- MYSQL_PASSWORD=root
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
ports:
|
||||||
|
- "6010:8000"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
environment:
|
||||||
|
- DATABASE_HOST=db
|
||||||
|
- DATABASE_NAME=mis
|
||||||
|
- DATABASE_USER=root
|
||||||
|
- DATABASE_PASSWORD=root
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: mysql:5.7
|
||||||
|
command: --default-authentication-plugin=mysql_native_password
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
- ./Install/SQL:/docker-entrypoint-initdb.d
|
||||||
|
environment:
|
||||||
|
- MYSQL_DATABASE=mis
|
||||||
|
- MYSQL_ROOT_PASSWORD=root
|
||||||
|
- MYSQL_PASSWORD=root
|
||||||
|
ports:
|
||||||
|
- "6011:3306"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'hr.apps.MyAppConfig'
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# coding = utf-8
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import generic
|
||||||
|
from common import const
|
||||||
|
from hr.models import Entry,SalaryItem,EmployeeSalaryItem
|
||||||
|
|
||||||
|
|
||||||
|
class SalaryItemAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['code','classification','name','plus_or_minus','required']
|
||||||
|
list_display_links = ['code','name']
|
||||||
|
list_per_page = 20
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeSalaryItemInline(admin.TabularInline):
|
||||||
|
model = EmployeeSalaryItem
|
||||||
|
exclude = ['employee']
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||||
|
if db_field.name == 'salary_item':
|
||||||
|
kwargs['queryset'] = SalaryItem.objects.filter(required=1)
|
||||||
|
|
||||||
|
return super(EmployeeSalaryItemInline,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
|
||||||
|
class EntryAdmin(generic.BOAdmin):
|
||||||
|
list_display = ['code','name','gender','position','rank','probation_months','probation_end']
|
||||||
|
inlines = [EmployeeSalaryItemInline]
|
||||||
|
fieldsets = [
|
||||||
|
(None,{'fields':[('code','position',),('name','pinyin',),('address','zipcode',),('idcard','phone',),('memo',),('profile',)]}),
|
||||||
|
(_('org distribute'),{'fields':[('guider',),('rank','ygxs',),('category','probation_months',),('probation_begin','probation_end',)],'classes': ['collapse']})
|
||||||
|
]
|
||||||
|
raw_id_fields = ['position','guider']
|
||||||
|
def get_changeform_initial_data(self, request):
|
||||||
|
import datetime
|
||||||
|
end = datetime.date.today()+datetime.timedelta(90)
|
||||||
|
return {'probation_end':end}
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(SalaryItem,SalaryItemAdmin)
|
||||||
|
admin.site.register(Entry,EntryAdmin)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# created at 15-5-23
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class MyAppConfig(AppConfig):
|
||||||
|
name = 'hr'
|
||||||
|
verbose_name = _("human resource")
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import generic
|
||||||
|
from common import const
|
||||||
|
from basedata.models import Position,Employee
|
||||||
|
from organ.models import OrgUnit
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class SalaryItemHandler:
|
||||||
|
code = None
|
||||||
|
|
||||||
|
def __init__(self,employee):
|
||||||
|
self.employee = employee
|
||||||
|
|
||||||
|
def value(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class SalaryItem(models.Model):
|
||||||
|
"""
|
||||||
|
工资项
|
||||||
|
"""
|
||||||
|
formulas = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_formula(cls, code, handler):
|
||||||
|
SalaryItem.formulas[code] = handler
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_formula(cls):
|
||||||
|
return SalaryItem.formulas.get(cls.code,None)
|
||||||
|
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_10,null=True)
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
classification = models.CharField(_("classification"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S048'),default='10')
|
||||||
|
plus_or_minus = models.CharField(_("plus or minus"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S049'),default='+')
|
||||||
|
required = models.BooleanField(_("is required"),default=0)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return "%s %s" % (self.code,self.name)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('salary item')
|
||||||
|
verbose_name_plural = _('salary items')
|
||||||
|
ordering = ('code',)
|
||||||
|
|
||||||
|
|
||||||
|
class Entry(generic.BO):
|
||||||
|
"""
|
||||||
|
人员入职
|
||||||
|
"""
|
||||||
|
code = models.CharField(_("employee number"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
name = models.CharField(_("employee name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
birthday = models.DateField(_("birthday"),blank=True,null=True)
|
||||||
|
gender = models.CharField(_("gender"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('gender'),default='1')
|
||||||
|
idcard = models.CharField(_("id card"),max_length=const.DB_CHAR_NAME_20)
|
||||||
|
address = models.CharField(_("mail address"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
zipcode = models.CharField(_("zipcode"),max_length=const.DB_CHAR_CODE_8)
|
||||||
|
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
|
||||||
|
guider = models.ForeignKey(Employee,verbose_name=_("guider"))
|
||||||
|
position = models.ForeignKey(Position,verbose_name = _('designate position'))
|
||||||
|
rank = models.CharField(_("employee rank"),max_length=const.DB_CHAR_CODE_2,default='00',choices=const.get_value_list('S017'))
|
||||||
|
ygxs = models.CharField(_("employ ygxs"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S019'),default='2')
|
||||||
|
category = models.CharField(_("employ category"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=const.get_value_list('S018'),default='21')
|
||||||
|
|
||||||
|
probation_months = models.CharField(_("probation months"),max_length=2,default='3',choices=const.get_value_list('S047'))
|
||||||
|
probation_begin = models.DateField(_("probation begin"),default=datetime.date.today)
|
||||||
|
probation_end = models.DateField(_("probation end"),blank=True,null=True)
|
||||||
|
|
||||||
|
memo = models.TextField(_("memo"),blank=True,null=True)
|
||||||
|
profile = models.FileField(_("profile"),blank=True,null=True,upload_to='hr profile')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("employee entry")
|
||||||
|
verbose_name_plural = _("employee entries")
|
||||||
|
permissions = (
|
||||||
|
('modify_salary_item',_("modify salary item")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeSalaryItem(models.Model):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
entry = models.ForeignKey(Entry,verbose_name=_("employee entry"))
|
||||||
|
employee = models.ForeignKey(Employee,verbose_name=_("employee"),blank=True,null=True)
|
||||||
|
salary_item = models.ForeignKey(SalaryItem,verbose_name=_("salary item"))
|
||||||
|
calculate_way = models.CharField(_("calculate way"),max_length=const.DB_CHAR_CODE_2,choices=const.get_value_list('S050'),default='10')
|
||||||
|
fixed_value = models.DecimalField(_("fixed value"),blank=True,null=True,max_digits=10,decimal_places=2)
|
||||||
|
base_value = models.DecimalField(_("base value"),blank=True,null=True,max_digits=10,decimal_places=2)
|
||||||
|
org_percent = models.DecimalField(_("org percent"),blank=True,null=True,max_digits=4,decimal_places=2)
|
||||||
|
employee_percent = models.DecimalField(_("employee percent"),blank=True,null=True,max_digits=4,decimal_places=2)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("salary item")
|
||||||
|
verbose_name_plural = _("salary item")
|
||||||
|
unique_together = (('entry', 'salary_item'),)
|
||||||
|
|
||||||
|
|
||||||
|
class Transfer(generic.BO):
|
||||||
|
"""
|
||||||
|
人员调动
|
||||||
|
"""
|
||||||
|
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("employee transfer")
|
||||||
|
verbose_name_plural = _("employee transfers")
|
||||||
|
|
||||||
|
|
||||||
|
class Departure(generic.BO):
|
||||||
|
"""
|
||||||
|
人员离职
|
||||||
|
"""
|
||||||
|
employee = models.ForeignKey(Employee,verbose_name=_("employee"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("employee departure")
|
||||||
|
verbose_name_plural = _("employee departures")
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
After Width: | Height: | Size: 914 KiB |
|
After Width: | Height: | Size: 234 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 123 KiB |
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = "invent.apps.MyAppConfig"
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import generic
|
||||||
|
from basedata.models import Material
|
||||||
|
from invent.models import StockIn,StockOut,InitialInventory,InItem,OutItem,InitItem,Inventory,InItemForm,InOutDetail,\
|
||||||
|
WareReturn,ReturnItem,WareAdjust,AdjustItem
|
||||||
|
|
||||||
|
|
||||||
|
class InItemInline(admin.TabularInline):
|
||||||
|
model = InItem
|
||||||
|
form = InItemForm
|
||||||
|
fields = ('material', 'measure', 'cnt', 'price')
|
||||||
|
raw_id_fields = ['material']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
if obj and obj.status == 1:
|
||||||
|
return ['material', 'measure', 'cnt', 'price']
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||||
|
if db_field.name == 'material':
|
||||||
|
kwargs['queryset'] = Material.objects.filter(is_virtual=0)
|
||||||
|
return super(InItemInline,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class OutItemInline(admin.TabularInline):
|
||||||
|
model = OutItem
|
||||||
|
fields = ('inventory', 'measure', 'cnt', 'price','warehouse',)
|
||||||
|
raw_id_fields = ['inventory']
|
||||||
|
readonly_fields = ['measure', 'price', 'warehouse']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
|
||||||
|
class InitItemInline(admin.TabularInline):
|
||||||
|
model = InitItem
|
||||||
|
fields = ('material', 'measure', 'cnt', 'warehouse', 'price',)
|
||||||
|
raw_id_fields = ['material']
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
if obj and obj.execute_time:
|
||||||
|
return ['material', 'measure', 'cnt', 'warehouse', 'price']
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||||
|
if db_field.name == 'material':
|
||||||
|
kwargs['queryset'] = Material.objects.filter(is_virtual=0)
|
||||||
|
return super(InitItemInline,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class StockInAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'RK'
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
list_display = ['code','title','money_of_amount','status','entry_time']
|
||||||
|
inlines = [InItemInline]
|
||||||
|
raw_id_fields = ['po']
|
||||||
|
fields = (
|
||||||
|
('code',),('title',),('po',),('warehouse',),('batch',),('status','amount',)
|
||||||
|
)
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
extra_buttons = [{'href':'cin','title':_('Action Stock In')}]
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
import decimal
|
||||||
|
super(StockInAdmin,self).save_model(request,obj,form,change)
|
||||||
|
if obj and obj.po:
|
||||||
|
po_items = obj.po.poitem_set.filter(left_cnt__gt=0).all()
|
||||||
|
for item in po_items:
|
||||||
|
try:
|
||||||
|
InItem.objects.get(po_item=item,master=obj)
|
||||||
|
continue
|
||||||
|
except Exception,e:
|
||||||
|
pp = item.discount_price or item.price
|
||||||
|
if decimal.Decimal(item.tax) > decimal.Decimal(0):
|
||||||
|
pp = pp /(decimal.Decimal(1)+decimal.Decimal(item.tax))
|
||||||
|
InItem.objects.create(warehouse=obj.warehouse,material=item.material,measure=item.measure,prop='+',
|
||||||
|
po_item=item,master=obj,cnt=item.left_cnt,price=pp,batch=obj.batch,source=obj.code)
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
print obj
|
||||||
|
if obj and obj.status == 9:
|
||||||
|
return ['code','title','po','warehouse','batch','status']
|
||||||
|
else:
|
||||||
|
return ['status','amount']
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
if object_id:
|
||||||
|
obj = StockIn.objects.get(id=object_id)
|
||||||
|
if obj and obj.execute_time:
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
return super(StockInAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
class StockOutAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'CK'
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
list_display = ['code','title','project','status','out_time','out_amount']
|
||||||
|
list_display_links = ['code','title']
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
inlines = [OutItemInline]
|
||||||
|
raw_id_fields = ['project','wo','user']
|
||||||
|
fields = (
|
||||||
|
('code', 'status',),('project', ),('wo','user'),
|
||||||
|
('title','amount',),('description',),
|
||||||
|
)
|
||||||
|
readonly_fields = ['status']
|
||||||
|
extra_buttons = [{'href':'out','title':_('Action Stock Out')}]
|
||||||
|
search_fields = ['code','title','user__username']
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
if object_id:
|
||||||
|
obj = StockOut.objects.get(id=object_id)
|
||||||
|
if obj and obj.execute_time:
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
return super(StockOutAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if obj and obj.user is None:
|
||||||
|
obj.user = request.user
|
||||||
|
super(StockOutAdmin,self).save_model(request,obj,form,change)
|
||||||
|
|
||||||
|
|
||||||
|
class InitialInventoryAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'QC'
|
||||||
|
CODE_NUMBER_WIDTH = 3
|
||||||
|
list_display = ['code','title','status']
|
||||||
|
inlines = [InitItemInline]
|
||||||
|
fields = ('code','title','org','status','amount','attach')
|
||||||
|
readonly_fields = ['status','amount']
|
||||||
|
extra_buttons = [{'href':'cin','title':_('Action Stock In')}]
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
if object_id:
|
||||||
|
obj = InitialInventory.objects.get(id=object_id)
|
||||||
|
if obj and obj.execute_time:
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
return super(InitialInventoryAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryAdmin(generic.BOAdmin):
|
||||||
|
list_display = ['material','measure','warehouse','cnt','price']
|
||||||
|
search_fields = ['material__name','material__code']
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
if object_id:
|
||||||
|
inventory = Inventory.objects.get(id=object_id)
|
||||||
|
material = inventory.material
|
||||||
|
warehouse = inventory.warehouse
|
||||||
|
detail = InOutDetail.objects.filter(material=material,warehouse=warehouse)
|
||||||
|
extra_context.update(dict(detail=detail))
|
||||||
|
|
||||||
|
return super(InventoryAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnItemInline(admin.TabularInline):
|
||||||
|
model = ReturnItem
|
||||||
|
fields = ['material','measure','warehouse','out_cnt','cnt']
|
||||||
|
readonly_fields = ['material','measure','warehouse','out_cnt']
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
class WareReturnAdmin(generic.BOAdmin):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
CODE_PREFIX = 'FK'
|
||||||
|
CODE_NUMBER_WIDTH = 4
|
||||||
|
list_display = ['code','title','out']
|
||||||
|
fields = (
|
||||||
|
('code',),('title',),('out',),('amount',),('status',)
|
||||||
|
)
|
||||||
|
readonly_fields = ['status']
|
||||||
|
raw_id_fields = ['out']
|
||||||
|
inlines = [ReturnItemInline]
|
||||||
|
extra_buttons = [{'href':'cin','title':_('Action Ware Return')}]
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
if object_id :
|
||||||
|
obj = WareReturn.objects.get(id=object_id)
|
||||||
|
if obj.status == '9':
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
return super(WareReturnAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
class AdjustItemInline(admin.TabularInline):
|
||||||
|
model = AdjustItem
|
||||||
|
fields = ['inventory','measure','warehouse','prop','cnt']
|
||||||
|
readonly_fields = ['measure','warehouse']
|
||||||
|
raw_id_fields = ['inventory']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class WareAdjustAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'TZ'
|
||||||
|
CODE_NUMBER_WIDTH = 3
|
||||||
|
list_display = ['code','title','status']
|
||||||
|
fields = (
|
||||||
|
('code',),('title',),('description',),('status',)
|
||||||
|
)
|
||||||
|
readonly_fields = ['status']
|
||||||
|
inlines = [AdjustItemInline]
|
||||||
|
extra_buttons = [{'href':'adjust','title':_('Action Ware Adjust')}]
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
if object_id:
|
||||||
|
obj = WareAdjust.objects.get(id=object_id)
|
||||||
|
if obj and obj.status == '9':
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
return super(WareAdjustAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(StockIn,StockInAdmin)
|
||||||
|
admin.site.register(StockOut,StockOutAdmin)
|
||||||
|
admin.site.register(InitialInventory,InitialInventoryAdmin)
|
||||||
|
admin.site.register(Inventory,InventoryAdmin)
|
||||||
|
admin.site.register(WareReturn,WareReturnAdmin)
|
||||||
|
admin.site.register(WareAdjust,WareAdjustAdmin)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# created at 15-5-23
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class MyAppConfig(AppConfig):
|
||||||
|
name = 'invent'
|
||||||
|
verbose_name = _("inventory manage")
|
||||||
@@ -0,0 +1,493 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
import decimal
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db import models
|
||||||
|
from django import forms
|
||||||
|
from mis import settings
|
||||||
|
from django.utils.text import force_text
|
||||||
|
from django.db import transaction
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import generic
|
||||||
|
from common import const
|
||||||
|
from basedata.models import Material,Warehouse,Measure,Organization,Project
|
||||||
|
from selfhelp.models import WorkOrder
|
||||||
|
from purchase.models import PurchaseOrder,POItem
|
||||||
|
|
||||||
|
|
||||||
|
class Inventory(generic.BO):
|
||||||
|
"""
|
||||||
|
库存信息
|
||||||
|
"""
|
||||||
|
index_weight = 1
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
warehouse = models.ForeignKey(Warehouse,verbose_name=_("warehouse"))
|
||||||
|
material = models.ForeignKey(Material,verbose_name=_("material"))
|
||||||
|
measure = models.ForeignKey(Measure,verbose_name=_("measure"))
|
||||||
|
cnt = models.DecimalField(_("count"),max_digits=14,decimal_places=4)
|
||||||
|
batch = models.CharField(_("batch"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
price = models.DecimalField(_("price"),max_digits=14,decimal_places=4)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
des = self.material.spec or ''
|
||||||
|
return u'%s %s %s' % (self.material.code,self.material.name,des)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Inventory")
|
||||||
|
verbose_name_plural = _("Inventory")
|
||||||
|
ordering = ['material']
|
||||||
|
|
||||||
|
|
||||||
|
class InitialInventory(generic.BO):
|
||||||
|
"""
|
||||||
|
期初库存
|
||||||
|
"""
|
||||||
|
STATUS = (
|
||||||
|
('0', _("NEW")),
|
||||||
|
('9', _("EXECUTED"))
|
||||||
|
)
|
||||||
|
index_weight = 9
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||||
|
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||||
|
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||||
|
attach = models.FileField(_('attach'),blank=True,null=True,upload_to='inventory',help_text=u'参考FD0002模板文档')
|
||||||
|
amount = models.DecimalField(_('money of amount'),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
|
||||||
|
super(InitialInventory,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
count = InitItem.objects.filter(master=self).count()
|
||||||
|
if self.attach and count == 0:
|
||||||
|
path = os.path.join(settings.MEDIA_ROOT,self.attach.name)
|
||||||
|
reader = csv.reader(open(path,'r'))
|
||||||
|
index = 0
|
||||||
|
with transaction.atomic():
|
||||||
|
for code,name,description,unit_code,unit_name,warehouse_code,warehouse_name,price,cnt in reader:
|
||||||
|
index+=1
|
||||||
|
if index == 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
material = None
|
||||||
|
measure = None
|
||||||
|
house = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
measure = Measure.objects.get(code=unit_code)
|
||||||
|
except Exception,e:
|
||||||
|
measure = Measure.objects.create(code=unit_code,name=force_text(unit_name.decode('gbk')))
|
||||||
|
|
||||||
|
try:
|
||||||
|
house = Warehouse.objects.get(code=warehouse_code)
|
||||||
|
except Exception,e:
|
||||||
|
house = Warehouse.objects.create(code=warehouse_code,name=force_text(warehouse_name.decode('gbk')))
|
||||||
|
|
||||||
|
try:
|
||||||
|
material = Material.objects.get(code=code)
|
||||||
|
except Exception,e:
|
||||||
|
material = Material(code=code,name=force_text(name.decode('gbk')),spec=force_text(description.decode('gbk')),warehouse=house)
|
||||||
|
material.stock_price = price
|
||||||
|
material.save()
|
||||||
|
InitItem.objects.create(master=self,material=material,price=price,cnt=cnt,warehouse=house,measure=measure,source=self.code)
|
||||||
|
|
||||||
|
def init_entry(self,request=None):
|
||||||
|
"""
|
||||||
|
执行期初入库
|
||||||
|
"""
|
||||||
|
count = InitItem.objects.filter(master=self).count()
|
||||||
|
if count > 0:
|
||||||
|
with transaction.atomic():
|
||||||
|
total_amount = 0
|
||||||
|
for item in InitItem.objects.filter(master=self).all():
|
||||||
|
Inventory.objects.create(material=item.material,price=item.price,cnt=item.cnt,warehouse=item.warehouse,measure=item.measure)
|
||||||
|
item.status = 1
|
||||||
|
item.event_time = datetime.datetime.now()
|
||||||
|
item.source = self.code
|
||||||
|
item.save()
|
||||||
|
item.material.stock_price = item.price
|
||||||
|
if item.material.measure.count() == 0:
|
||||||
|
item.material.measure.add(item.measure)
|
||||||
|
item.material.save()
|
||||||
|
total_amount += item.price*item.cnt
|
||||||
|
self.amount = total_amount
|
||||||
|
self.status = '9'
|
||||||
|
self.execute_time = datetime.datetime.now()
|
||||||
|
self.save()
|
||||||
|
else:
|
||||||
|
raise Exception(_("none material was found"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Initial Inventory")
|
||||||
|
verbose_name_plural = _("Initial Inventory")
|
||||||
|
|
||||||
|
|
||||||
|
class StockIn(generic.BO):
|
||||||
|
"""
|
||||||
|
入库单
|
||||||
|
"""
|
||||||
|
STATUS = (
|
||||||
|
('0', _("NEW")),
|
||||||
|
('1', _("QUALITY TESTING")),
|
||||||
|
('9', _("EXECUTED"))
|
||||||
|
)
|
||||||
|
index_weight = 3
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
warehouse = models.ForeignKey(Warehouse,verbose_name=_("warehouse"))
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||||
|
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||||
|
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||||
|
po = models.ForeignKey(PurchaseOrder,verbose_name=_("purchase order"),null=True,limit_choices_to={"entry_status":"0"},blank=True)
|
||||||
|
amount = models.DecimalField(_("stock in money of amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
batch = models.CharField(_("batch"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
|
||||||
|
def money_of_amount(self):
|
||||||
|
if self.amount:
|
||||||
|
return self.amount
|
||||||
|
else:
|
||||||
|
return 0.00
|
||||||
|
|
||||||
|
def entry_time(self):
|
||||||
|
if self.execute_time:
|
||||||
|
return self.execute_time
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
money_of_amount.short_description = _("stock in money of amount")
|
||||||
|
entry_time.short_description = _("entry time")
|
||||||
|
|
||||||
|
def action_entry(self,request):
|
||||||
|
"""
|
||||||
|
执行入库操作
|
||||||
|
"""
|
||||||
|
if self.initem_set.count() > 0:
|
||||||
|
with transaction.atomic():
|
||||||
|
total_amount = decimal.Decimal(0)
|
||||||
|
for item in self.initem_set.filter(status=0).all():
|
||||||
|
try:
|
||||||
|
inventory = Inventory.objects.get(warehouse=self.warehouse,material=item.material,measure=item.measure)
|
||||||
|
if inventory.price != item.price:
|
||||||
|
at = decimal.Decimal(inventory.price*inventory.cnt+item.price*item.cnt)
|
||||||
|
ac = decimal.Decimal(inventory.cnt+item.cnt)
|
||||||
|
average = at/ac
|
||||||
|
inventory.price = average
|
||||||
|
item.material.stock_price = average
|
||||||
|
item.material.save()
|
||||||
|
inventory.cnt += item.cnt
|
||||||
|
inventory.save()
|
||||||
|
total_amount += item.price*item.cnt
|
||||||
|
except Exception,e:
|
||||||
|
Inventory.objects.create(warehouse=self.warehouse,material=item.material,measure=item.measure,
|
||||||
|
cnt=item.cnt,price=item.price,org=self.org)
|
||||||
|
item.material.stock_price = item.price
|
||||||
|
item.material.save()
|
||||||
|
|
||||||
|
item.status=1
|
||||||
|
item.event_time = datetime.datetime.now()
|
||||||
|
item.source = self.code
|
||||||
|
item.save()
|
||||||
|
total_amount += item.price*item.cnt
|
||||||
|
# saving the purchase item
|
||||||
|
item.po_item.is_in_stock = 1
|
||||||
|
item.po_item.in_stock_time = datetime.datetime.now()
|
||||||
|
item.po_item.entry_cnt = item.cnt
|
||||||
|
|
||||||
|
item.po_item.save()
|
||||||
|
none_zero_left = POItem.objects.filter(po=item.po_item.po,left_cnt__gt=0).count()
|
||||||
|
if none_zero_left == 0:
|
||||||
|
item.po_item.po.status = '99'
|
||||||
|
item.po_item.po.entry_status = 1
|
||||||
|
item.po_item.po.entry_time = datetime.datetime.now()
|
||||||
|
item.po_item.po.save()
|
||||||
|
# saving stock in item status
|
||||||
|
item.status = 1
|
||||||
|
item.event_time = datetime.datetime.now()
|
||||||
|
# updating master's record
|
||||||
|
self.status = 9
|
||||||
|
self.execute_time = datetime.datetime.now()
|
||||||
|
self.amount = total_amount
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("StockIn")
|
||||||
|
verbose_name_plural = _("StockIn")
|
||||||
|
|
||||||
|
|
||||||
|
class StockOut(generic.BO):
|
||||||
|
"""
|
||||||
|
领料单
|
||||||
|
"""
|
||||||
|
STATUS = (
|
||||||
|
('0', _("NEW")),
|
||||||
|
('1', _("IN PROGRESS")),
|
||||||
|
('9', _("EXECUTED"))
|
||||||
|
)
|
||||||
|
index_weight = 2
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
project = models.ForeignKey(Project,verbose_name=_("project"),blank=True,null=True)
|
||||||
|
wo = models.ForeignKey(WorkOrder,verbose_name=_("work order"),blank=True,null=True)
|
||||||
|
description = models.TextField(_("description"),blank=True,null=True)
|
||||||
|
amount = models.DecimalField(_("money of amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("out user"),blank=True,null=True)
|
||||||
|
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||||
|
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||||
|
|
||||||
|
def out_amount(self):
|
||||||
|
return self.amount or ''
|
||||||
|
|
||||||
|
def out_time(self):
|
||||||
|
return self.execute_time or ''
|
||||||
|
|
||||||
|
out_time.short_description = _('stock out time')
|
||||||
|
out_amount.short_description = _('stock out amount')
|
||||||
|
|
||||||
|
def action_out(self,request=None):
|
||||||
|
"""
|
||||||
|
执行出库操作
|
||||||
|
"""
|
||||||
|
if self.outitem_set.count() > 0:
|
||||||
|
with transaction.atomic():
|
||||||
|
total = decimal.Decimal(0)
|
||||||
|
for item in OutItem.objects.filter(master=self).all():
|
||||||
|
if item.inventory.cnt < item.cnt:
|
||||||
|
raise Exception('%s does not meets required' % item.material)
|
||||||
|
if item.cnt:
|
||||||
|
total += item.cnt * item.inventory.price
|
||||||
|
item.inventory.cnt -= item.cnt
|
||||||
|
item.status = 1
|
||||||
|
item.price = item.inventory.price
|
||||||
|
item.event_time = datetime.datetime.now()
|
||||||
|
item.inventory.save()
|
||||||
|
item.source=self.code
|
||||||
|
item.save()
|
||||||
|
if total > 0:
|
||||||
|
self.amount = total
|
||||||
|
self.status = '9'
|
||||||
|
self.execute_time = datetime.datetime.now()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("StockOut")
|
||||||
|
verbose_name_plural = _("StockOut")
|
||||||
|
|
||||||
|
|
||||||
|
class WareReturn(generic.BO):
|
||||||
|
"""
|
||||||
|
返库单
|
||||||
|
"""
|
||||||
|
STATUS = (
|
||||||
|
('0', _("NEW")),
|
||||||
|
('1', _("IN PROGRESS")),
|
||||||
|
('9', _("EXECUTED"))
|
||||||
|
)
|
||||||
|
index_weight = 5
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
out = models.ForeignKey(StockOut,verbose_name=_('StockOut'))
|
||||||
|
amount = models.DecimalField(_("money of amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("out user"),blank=True,null=True)
|
||||||
|
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||||
|
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
super(WareReturn,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
item_count = ReturnItem.objects.filter(master=self).count()
|
||||||
|
if self.out and item_count == 0:
|
||||||
|
for out_item in OutItem.objects.filter(master=self.out):
|
||||||
|
ReturnItem.objects.create(master=self,out_item=out_item,material=out_item.material,price=out_item.price,
|
||||||
|
measure=out_item.measure,warehouse=out_item.warehouse,out_cnt=out_item.cnt,cnt=out_item.cnt)
|
||||||
|
|
||||||
|
def action_return(self,request):
|
||||||
|
with transaction.atomic():
|
||||||
|
total_amount = decimal.Decimal(0)
|
||||||
|
for item in ReturnItem.objects.filter(master=self):
|
||||||
|
if item.cnt > item.out_item.cnt or item.cnt < 0:
|
||||||
|
raise Exception('%s cnt is invalid,out is %s,return is %s' % (item.material,item.out_cnt,item.cnt))
|
||||||
|
item.event_time = datetime.datetime.now()
|
||||||
|
item.source = self.code
|
||||||
|
item.status = 1
|
||||||
|
item.out_item.inventory.cnt += item.cnt
|
||||||
|
item.out_item.inventory.save()
|
||||||
|
item.save()
|
||||||
|
total_amount += item.price * item.cnt
|
||||||
|
self.amount = total_amount
|
||||||
|
self.status = '9'
|
||||||
|
self.execute_time = datetime.datetime.now()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("ware return")
|
||||||
|
verbose_name_plural = _("ware return")
|
||||||
|
|
||||||
|
|
||||||
|
class WareAdjust(generic.BO):
|
||||||
|
"""
|
||||||
|
库存调整
|
||||||
|
"""
|
||||||
|
STATUS = (
|
||||||
|
('0', _("NEW")),
|
||||||
|
('1', _("IN PROGRESS")),
|
||||||
|
('9', _("EXECUTED"))
|
||||||
|
)
|
||||||
|
index_weight = 4
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
description = models.TextField(_("description"),blank=True,null=True)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("out user"),blank=True,null=True)
|
||||||
|
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||||
|
execute_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||||
|
|
||||||
|
def action_adjust(self,request):
|
||||||
|
with transaction.atomic():
|
||||||
|
for item in AdjustItem.objects.filter(master=self,status=0):
|
||||||
|
inventory = item.inventory
|
||||||
|
if item.prop == '+':
|
||||||
|
inventory.cnt += item.cnt
|
||||||
|
else:
|
||||||
|
inventory.cnt -= item.cnt
|
||||||
|
inventory.save()
|
||||||
|
item.status = 1
|
||||||
|
item.event_time = datetime.datetime.now()
|
||||||
|
item.source = self.code
|
||||||
|
item.save()
|
||||||
|
self.status = '9'
|
||||||
|
self.execute_time = datetime.datetime.now()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("ware adjust")
|
||||||
|
verbose_name_plural = _("ware adjust")
|
||||||
|
|
||||||
|
|
||||||
|
class InOutDetail(models.Model):
|
||||||
|
"""
|
||||||
|
in out detail
|
||||||
|
"""
|
||||||
|
|
||||||
|
PROP = (
|
||||||
|
('+', _("PLUS")),
|
||||||
|
('-', _("MINUS"))
|
||||||
|
)
|
||||||
|
|
||||||
|
create_time = models.DateTimeField(_("create time"),auto_now_add=True)
|
||||||
|
status = models.BooleanField(_("executed"),default=0)
|
||||||
|
event_time = models.DateTimeField(_("event time"),blank=True,null=True)
|
||||||
|
warehouse = models.ForeignKey(Warehouse,verbose_name=_("warehouse"),blank=True,null=True)
|
||||||
|
material = models.ForeignKey(Material,verbose_name=_("material"),limit_choices_to={"is_virtual":"0"},blank=True,null=True)
|
||||||
|
measure = models.ForeignKey(Measure,verbose_name=_("measure"),blank=True,null=True)
|
||||||
|
cnt = models.DecimalField(_("count"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
batch = models.CharField(_("batch"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
price = models.DecimalField(_("price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
prop = models.CharField(_("plus or minus property"),max_length=const.DB_CHAR_CODE_2,choices=PROP,default='+')
|
||||||
|
source = models.CharField(_("source"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
|
||||||
|
|
||||||
|
class InitItem(InOutDetail):
|
||||||
|
"""
|
||||||
|
期初入库明细
|
||||||
|
"""
|
||||||
|
master = models.ForeignKey(InitialInventory)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("init item")
|
||||||
|
verbose_name_plural = _("init item")
|
||||||
|
|
||||||
|
|
||||||
|
class InItem(InOutDetail):
|
||||||
|
"""
|
||||||
|
入库单明细
|
||||||
|
"""
|
||||||
|
master = models.ForeignKey(StockIn)
|
||||||
|
po_item = models.ForeignKey(POItem,verbose_name=_("po item"),blank=True,null=True)
|
||||||
|
|
||||||
|
def get_new_price(self):
|
||||||
|
if self.po_item and self.master.warehouse:
|
||||||
|
try:
|
||||||
|
inventory = Inventory.objects.get(warehouse=self.master.warehouse,material=self.material,measure=self.measure)
|
||||||
|
if inventory and self.price != inventory.price:
|
||||||
|
total_amount = self.price*self.cnt+inventory.price*inventory.cnt
|
||||||
|
total_count = self.cnt+inventory.cnt
|
||||||
|
return total_amount/total_count
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
return self.price or ''
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("in item")
|
||||||
|
verbose_name_plural = _("in item")
|
||||||
|
|
||||||
|
|
||||||
|
class InItemForm(forms.ModelForm):
|
||||||
|
|
||||||
|
new_price = forms.CharField(label=_('new price'),required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = InItem
|
||||||
|
fields = ('material', 'measure', 'cnt', 'price')
|
||||||
|
|
||||||
|
|
||||||
|
class OutItem(InOutDetail):
|
||||||
|
"""
|
||||||
|
出库单明细
|
||||||
|
"""
|
||||||
|
master = models.ForeignKey(StockOut)
|
||||||
|
inventory = models.ForeignKey(Inventory,blank=True,null=True,verbose_name=_("inventory material"))
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
self.prop = '-'
|
||||||
|
if self.inventory:
|
||||||
|
self.material = self.inventory.material
|
||||||
|
self.measure = self.inventory.measure
|
||||||
|
self.warehouse = self.inventory.warehouse
|
||||||
|
|
||||||
|
super(OutItem,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("out item")
|
||||||
|
verbose_name_plural = _("out item")
|
||||||
|
|
||||||
|
|
||||||
|
class ReturnItem(InOutDetail):
|
||||||
|
"""
|
||||||
|
返库单明细
|
||||||
|
"""
|
||||||
|
master = models.ForeignKey(WareReturn)
|
||||||
|
out_item = models.ForeignKey(OutItem,blank=True,null=True,verbose_name=_('out item'))
|
||||||
|
out_cnt = models.DecimalField(_("out count"),max_digits=14,decimal_places=4)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("return item")
|
||||||
|
verbose_name_plural = _("return item")
|
||||||
|
|
||||||
|
|
||||||
|
class AdjustItem(InOutDetail):
|
||||||
|
"""
|
||||||
|
库存调整明细
|
||||||
|
"""
|
||||||
|
master = models.ForeignKey(WareAdjust)
|
||||||
|
inventory = models.ForeignKey(Inventory,blank=True,null=True,verbose_name=_("inventory material"))
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if self.inventory:
|
||||||
|
self.material = self.inventory.material
|
||||||
|
self.measure = self.inventory.measure
|
||||||
|
self.warehouse = self.inventory.warehouse
|
||||||
|
super(AdjustItem,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("adjust item")
|
||||||
|
verbose_name_plural = _("adjust item")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from django.conf.urls import include, url,static
|
||||||
|
import invent.views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r"stockin/(?P<object_id>\d+)/cin", invent.views.action_in),
|
||||||
|
url(r"initialinventory/(?P<object_id>\d+)/cin", invent.views.action_init),
|
||||||
|
url(r"stockout/(?P<object_id>\d+)/out", invent.views.action_out),
|
||||||
|
url(r"warereturn/(?P<object_id>\d+)/cin", invent.views.action_return),
|
||||||
|
url(r"wareadjust/(?P<object_id>\d+)/adjust", invent.views.action_adjust),
|
||||||
|
]
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.contrib.admin import site
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db import connection
|
||||||
|
from django.http.response import HttpResponseRedirect
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from invent.models import StockIn,StockOut,InitialInventory,WareReturn,WareAdjust
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
def action_in(request,object_id):
|
||||||
|
"""
|
||||||
|
入库操作
|
||||||
|
:param request:
|
||||||
|
:param object_id:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
title = _("Are you sure?")
|
||||||
|
obj = StockIn.objects.get(id=int(object_id))
|
||||||
|
opts = obj._meta
|
||||||
|
objects_name = force_text(opts.verbose_name)
|
||||||
|
|
||||||
|
if request.POST.get("post"):
|
||||||
|
try:
|
||||||
|
obj.action_entry(request)
|
||||||
|
messages.success(request,_('check in successfully'))
|
||||||
|
except Exception,e:
|
||||||
|
messages.error(request,e)
|
||||||
|
|
||||||
|
return HttpResponseRedirect("/admin/invent/stockin/%s"%(object_id))
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
opts=opts,
|
||||||
|
objects_name=objects_name,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
request.current_app = site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def action_out(request,object_id):
|
||||||
|
"""
|
||||||
|
出库操作
|
||||||
|
:param request:
|
||||||
|
:param object_id:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
title = _("Are you sure?")
|
||||||
|
obj = StockOut.objects.get(id=int(object_id))
|
||||||
|
opts = obj._meta
|
||||||
|
objects_name = force_text(opts.verbose_name)
|
||||||
|
|
||||||
|
if request.POST.get("post"):
|
||||||
|
try:
|
||||||
|
obj.action_out(request)
|
||||||
|
messages.success(request,_('check out successfully'))
|
||||||
|
except Exception,e:
|
||||||
|
messages.error(request,e)
|
||||||
|
return HttpResponseRedirect("/admin/invent/stockout/%s"%(object_id))
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
opts=opts,
|
||||||
|
objects_name=objects_name,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
request.current_app = site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,'admin/invent/stockout/out_confirmation.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def action_init(request,object_id):
|
||||||
|
"""
|
||||||
|
期初入库操作
|
||||||
|
:param request:
|
||||||
|
:param object_id:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
title = _("Are you sure?")
|
||||||
|
obj = InitialInventory.objects.get(id=int(object_id))
|
||||||
|
opts = obj._meta
|
||||||
|
objects_name = force_text(opts.verbose_name)
|
||||||
|
|
||||||
|
if request.POST.get("post"):
|
||||||
|
try:
|
||||||
|
obj.init_entry(request)
|
||||||
|
messages.success(request,_('check in successfully'))
|
||||||
|
except Exception,e:
|
||||||
|
messages.error(request,e)
|
||||||
|
|
||||||
|
return HttpResponseRedirect("/admin/invent/initialinventory/%s"%(object_id))
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
opts=opts,
|
||||||
|
objects_name=objects_name,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
request.current_app = site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def action_return(request,object_id):
|
||||||
|
"""
|
||||||
|
返库操作
|
||||||
|
:param request:
|
||||||
|
:param object_id:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
title = _("Are you sure?")
|
||||||
|
obj = WareReturn.objects.get(id=int(object_id))
|
||||||
|
opts = obj._meta
|
||||||
|
objects_name = force_text(opts.verbose_name)
|
||||||
|
|
||||||
|
if request.POST.get("post"):
|
||||||
|
try:
|
||||||
|
obj.action_return(request)
|
||||||
|
messages.success(request,_('check in successfully'))
|
||||||
|
except Exception,e:
|
||||||
|
messages.error(request,e)
|
||||||
|
|
||||||
|
return HttpResponseRedirect("/admin/invent/warereturn/%s"%(object_id))
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
opts=opts,
|
||||||
|
objects_name=objects_name,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
request.current_app = site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def action_adjust(request,object_id):
|
||||||
|
"""
|
||||||
|
调整操作
|
||||||
|
:param request:
|
||||||
|
:param object_id:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
title = _("Are you sure?")
|
||||||
|
obj = WareAdjust.objects.get(id=int(object_id))
|
||||||
|
opts = obj._meta
|
||||||
|
objects_name = force_text(opts.verbose_name)
|
||||||
|
|
||||||
|
if request.POST.get("post"):
|
||||||
|
try:
|
||||||
|
obj.action_adjust(request)
|
||||||
|
messages.success(request,_('check in successfully'))
|
||||||
|
except Exception,e:
|
||||||
|
messages.error(request,e)
|
||||||
|
|
||||||
|
return HttpResponseRedirect("/admin/invent/wareadjust/%s"%(object_id))
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
opts=opts,
|
||||||
|
objects_name=objects_name,
|
||||||
|
object=obj,
|
||||||
|
)
|
||||||
|
request.current_app = site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mis.settings")
|
||||||
|
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
__author__ = 'Administrator'
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
__author__ = 'zhugl'
|
||||||
|
# created at 15-4-21
|
||||||
|
#python import
|
||||||
|
from threading import local
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.apps import apps
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.admin import ModelAdmin, actions
|
||||||
|
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||||
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
|
from django.core.urlresolvers import NoReverseMatch, reverse
|
||||||
|
from django.db.models.base import ModelBase
|
||||||
|
from django.http import Http404, HttpResponseRedirect
|
||||||
|
from django.template.engine import Engine
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.utils import six
|
||||||
|
from django.utils.text import capfirst
|
||||||
|
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||||
|
from django.views.decorators.cache import never_cache
|
||||||
|
from django.views.decorators.csrf import csrf_protect
|
||||||
|
|
||||||
|
_thread_local = local()
|
||||||
|
|
||||||
|
|
||||||
|
def getuser():
|
||||||
|
return getattr(_thread_local,'user',None)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestUser(object):
|
||||||
|
|
||||||
|
def process_request(self,request):
|
||||||
|
django_user = getattr(request,'user',None)
|
||||||
|
|
||||||
|
if django_user is not None:
|
||||||
|
_thread_local.user = django_user
|
||||||
|
|
||||||
|
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||||
|
app_weight = {'selfhelp':1,'purchase':3,'sale':2,'invent':4,'organ':5,'basedata':6,'syscfg':7,'workflow':8}
|
||||||
|
if view_func.__name__ == 'index':
|
||||||
|
app_dict = {}
|
||||||
|
for model, model_admin in admin.site._registry.items():
|
||||||
|
app_label = model._meta.app_label
|
||||||
|
has_module_perms = model_admin.has_module_permission(request)
|
||||||
|
|
||||||
|
if has_module_perms:
|
||||||
|
perms = model_admin.get_model_perms(request)
|
||||||
|
if True in perms.values():
|
||||||
|
info = (app_label, model._meta.model_name)
|
||||||
|
model_dict = {
|
||||||
|
'name': capfirst(model._meta.verbose_name_plural),
|
||||||
|
'object_name': model._meta.object_name,
|
||||||
|
'perms': perms,
|
||||||
|
'weight':getattr(model,'index_weight',99)
|
||||||
|
}
|
||||||
|
if perms.get('change', False):
|
||||||
|
try:
|
||||||
|
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=admin.site.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
|
if perms.get('add', False):
|
||||||
|
try:
|
||||||
|
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=admin.site.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
|
if app_label in app_dict:
|
||||||
|
app_dict[app_label]['models'].append(model_dict)
|
||||||
|
else:
|
||||||
|
app_dict[app_label] = {
|
||||||
|
'name': apps.get_app_config(app_label).verbose_name,
|
||||||
|
'app_label': app_label,
|
||||||
|
'app_url': reverse(
|
||||||
|
'admin:app_list',
|
||||||
|
kwargs={'app_label': app_label},
|
||||||
|
current_app=admin.site.name,
|
||||||
|
),
|
||||||
|
'has_module_perms': has_module_perms,
|
||||||
|
'models': [model_dict],
|
||||||
|
'weight':app_weight.get(app_label,99)
|
||||||
|
}
|
||||||
|
|
||||||
|
app_list = list(six.itervalues(app_dict))
|
||||||
|
app_list.sort(key=lambda x: x['weight'])
|
||||||
|
|
||||||
|
for app in app_list:
|
||||||
|
app['models'].sort(key=lambda x: x['weight'])
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
maxi_app_list=app_list,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
todolist = self.get_my_task(request)
|
||||||
|
context.update(dict(todolist = todolist))
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
# print context
|
||||||
|
view_kwargs['extra_context'] = context
|
||||||
|
|
||||||
|
if view_func.__name__ == 'app_index':
|
||||||
|
app_label = view_kwargs['app_label']
|
||||||
|
app_name = apps.get_app_config(app_label).verbose_name
|
||||||
|
app_dict = {}
|
||||||
|
lib_dict = {}
|
||||||
|
for model, model_admin in admin.site._registry.items():
|
||||||
|
if model_admin.has_module_permission(request):
|
||||||
|
label = model._meta.app_label
|
||||||
|
is_current = False
|
||||||
|
if label == app_label:
|
||||||
|
is_current = True
|
||||||
|
lib_dict[label] = {
|
||||||
|
'name': apps.get_app_config(label).verbose_name,
|
||||||
|
'app_label': label,
|
||||||
|
'app_url': reverse(
|
||||||
|
'admin:app_list',
|
||||||
|
kwargs={'app_label': label},
|
||||||
|
current_app=admin.site.name,
|
||||||
|
),
|
||||||
|
'weight':app_weight.get(label,99),
|
||||||
|
'is_current':is_current,
|
||||||
|
}
|
||||||
|
if app_label == model._meta.app_label:
|
||||||
|
has_module_perms = model_admin.has_module_permission(request)
|
||||||
|
if not has_module_perms:
|
||||||
|
raise PermissionDenied
|
||||||
|
|
||||||
|
perms = model_admin.get_model_perms(request)
|
||||||
|
|
||||||
|
if True in perms.values():
|
||||||
|
info = (app_label, model._meta.model_name)
|
||||||
|
model_dict = {
|
||||||
|
'name': capfirst(model._meta.verbose_name_plural),
|
||||||
|
'object_name': model._meta.object_name,
|
||||||
|
'perms': perms,
|
||||||
|
'weight':getattr(model,'index_weight',99)
|
||||||
|
}
|
||||||
|
if perms.get('change'):
|
||||||
|
try:
|
||||||
|
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=admin.site.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
|
if perms.get('add'):
|
||||||
|
try:
|
||||||
|
model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=admin.site.name)
|
||||||
|
except NoReverseMatch:
|
||||||
|
pass
|
||||||
|
if app_dict:
|
||||||
|
app_dict['models'].append(model_dict),
|
||||||
|
else:
|
||||||
|
app_dict = {
|
||||||
|
'name': app_name,
|
||||||
|
'app_label': app_label,
|
||||||
|
'app_url': '',
|
||||||
|
'has_module_perms': has_module_perms,
|
||||||
|
'models': [model_dict],
|
||||||
|
}
|
||||||
|
if not app_dict:
|
||||||
|
raise Http404('The requested admin page does not exist.')
|
||||||
|
# Sort the models alphabetically within each app.
|
||||||
|
app_dict['models'].sort(key=lambda x: x['weight'])
|
||||||
|
|
||||||
|
app_lib = list(six.itervalues(lib_dict))
|
||||||
|
app_lib.sort(key=lambda x: x['weight'])
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
maxi_app_list=[app_dict],
|
||||||
|
app_lib=app_lib,
|
||||||
|
)
|
||||||
|
view_kwargs['extra_context'] = context
|
||||||
|
|
||||||
|
def get_my_task(self,request):
|
||||||
|
from workflow.models import TodoList
|
||||||
|
if request and request.user:
|
||||||
|
query = TodoList.objects.filter(user=request.user,status=0)
|
||||||
|
if query.count() == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return query.all()[:10]
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import pymysql
|
||||||
|
pymysql.install_as_MySQLdb()
|
||||||
|
#解决mysql-python的历史遗留问题(Yuri_2017-04-29)
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
admin.site.site_header = '智捷ERP'
|
||||||
|
admin.site.site_title = '智捷ERP'
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
"""
|
||||||
|
Django settings for mis project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 1.8.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.8/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/1.8/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
import os
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '_5%1a5zxdjsb-je@85!l34g--ve7!skhc%^c2n)3vqyhq)yq@c'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'syscfg',
|
||||||
|
'basedata',
|
||||||
|
'organ',
|
||||||
|
'workflow',
|
||||||
|
'selfhelp',
|
||||||
|
'hr',
|
||||||
|
'invent',
|
||||||
|
'purchase',
|
||||||
|
'sale',
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'midware.cuser.RequestUser',
|
||||||
|
)
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'mis.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
TEMPLATE_THEME = 'default'
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'mis.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'HOST': '172.31.16.165',
|
||||||
|
'NAME': 'erp',
|
||||||
|
'USER': 'maximus',
|
||||||
|
'PASSWORD': 'maximus1234',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'zh-CN'
|
||||||
|
|
||||||
|
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = False
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR,'static')
|
||||||
|
STATICFILES_DIRS = (
|
||||||
|
('css',os.path.join(STATIC_ROOT,'css')),
|
||||||
|
('js',os.path.join(STATIC_ROOT,'js')),
|
||||||
|
('img',os.path.join(STATIC_ROOT,'img')),
|
||||||
|
)
|
||||||
|
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR,'upload')
|
||||||
|
MEDIA_URL = '/upload/'
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
"""
|
||||||
|
Django settings for mis project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 1.8.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.8/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/1.8/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
import os
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '_5%1a5zxdjsb-je@85!l34g--ve7!skhc%^c2n)3vqyhq)yq@c'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'syscfg',
|
||||||
|
'basedata',
|
||||||
|
'organ',
|
||||||
|
'workflow',
|
||||||
|
'selfhelp',
|
||||||
|
'hr',
|
||||||
|
'invent',
|
||||||
|
'purchase',
|
||||||
|
'sale',
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'midware.cuser.RequestUser',
|
||||||
|
)
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'mis.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
TEMPLATE_THEME = 'default'
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'mis.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
|
'HOST': os.environ.get('DATABASE_HOST', 'localhost'),
|
||||||
|
'PORT': os.environ.get('DATABASE_PORT', '3306'),
|
||||||
|
'NAME': os.environ.get('DATABASE_NAME', 'mis'),
|
||||||
|
'USER': os.environ.get('DATABASE_USER', 'root'),
|
||||||
|
'PASSWORD': os.environ.get('DATABASE_PASSWORD', 'root'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'zh-hans'
|
||||||
|
|
||||||
|
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
|
||||||
|
|
||||||
|
TIME_ZONE = 'Asia/Shanghai'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = False
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR,'static')
|
||||||
|
STATICFILES_DIRS = (
|
||||||
|
('css',os.path.join(STATIC_ROOT,'css')),
|
||||||
|
('js',os.path.join(STATIC_ROOT,'js')),
|
||||||
|
('img',os.path.join(STATIC_ROOT,'img')),
|
||||||
|
)
|
||||||
|
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR,'upload')
|
||||||
|
MEDIA_URL = '/upload/'
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from django.conf.urls import include, url, static
|
||||||
|
from django.contrib import admin
|
||||||
|
from mis import settings
|
||||||
|
import workflow.views
|
||||||
|
import invent.urls
|
||||||
|
import basedata.urls
|
||||||
|
import selfhelp.urls
|
||||||
|
import mis.views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^$', mis.views.home),
|
||||||
|
url(r"^admin/(?P<app>\w+)/(?P<model>\w+)/(?P<object_id>\d+)/start", workflow.views.start),
|
||||||
|
url(r"^admin/(?P<app>\w+)/(?P<model>\w+)/(?P<object_id>\d+)/approve/(?P<operation>\d+)", workflow.views.approve),
|
||||||
|
url(r"^admin/(?P<app>\w+)/(?P<model>\w+)/(?P<object_id>\d+)/restart/(?P<instance>\d+)", workflow.views.restart),
|
||||||
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
|
url(r'^admin/invent/', include(invent.urls)),
|
||||||
|
url(r'^admin/basedata/', include(basedata.urls)),
|
||||||
|
url(r'^admin/selfhelp/', include(selfhelp.urls)),
|
||||||
|
]
|
||||||
|
urlpatterns += static.static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
|
||||||
|
urlpatterns += static.static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from django.http.response import HttpResponseRedirect
|
||||||
|
|
||||||
|
|
||||||
|
def home(request):
|
||||||
|
return HttpResponseRedirect("/admin")
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for mis project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mis.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'organ.apps.OrganConfig'
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.contrib import admin
|
||||||
|
from common import generic
|
||||||
|
from organ.models import Organization,OrgUnit,Position
|
||||||
|
from basedata.models import BankAccount
|
||||||
|
from basedata.admin import BankAccountInline
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class OrgAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'O'
|
||||||
|
CODE_NUMBER_WIDTH = 2
|
||||||
|
list_display = ['code','name','represent','lic_code','cer_code']
|
||||||
|
|
||||||
|
fields = (
|
||||||
|
('name','code',),('short','pinyin',),
|
||||||
|
('tax_num','tax_account',),('tax_address',),('represent','email',),
|
||||||
|
('address','zipcode',),('contacts','phone',),('fax','website',),
|
||||||
|
('lic_code','cer_code',),('license','certificate',),('status','weight',),
|
||||||
|
)
|
||||||
|
|
||||||
|
inlines = [BankAccountInline]
|
||||||
|
|
||||||
|
|
||||||
|
class OrgUnitAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'S'
|
||||||
|
CODE_NUMBER_WIDTH = 3
|
||||||
|
list_display = ['code','name','unit_type','parent']
|
||||||
|
list_display_links = ['code','name']
|
||||||
|
fields = (
|
||||||
|
('organization',),('parent',),('name','code',),('short','pinyin',),
|
||||||
|
('unit_type',),('status','virtual',),('phone','fax',),
|
||||||
|
('contacts','email',),('weight',),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
return super(OrgUnitAdmin,self).get_queryset(request).filter(end__gt=datetime.date.today())
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if obj.parent and obj.parent.organization:
|
||||||
|
obj.organization = obj.parent.organization
|
||||||
|
obj.save()
|
||||||
|
super(OrgUnitAdmin,self).save_model(request,obj,form,change)
|
||||||
|
|
||||||
|
|
||||||
|
class PositionAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'P'
|
||||||
|
CODE_NUMBER_WIDTH = 4
|
||||||
|
list_display = ['code','name','unit','series','grade','parent']
|
||||||
|
list_display_links = ['code','name']
|
||||||
|
fields = (
|
||||||
|
('unit',),('organization',),('parent',),('name','code',),('short','pinyin',),('series','grade',),
|
||||||
|
('virtual','status'),('description',),('qualification',),('document',),('weight',),
|
||||||
|
)
|
||||||
|
readonly_fields = ['organization']
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
return super(PositionAdmin,self).get_queryset(request).filter(end__gt=datetime.date.today())
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if obj.unit:
|
||||||
|
obj.organization = obj.unit.organization
|
||||||
|
obj.save()
|
||||||
|
super(PositionAdmin,self).save_model(request,obj,form,change)
|
||||||
|
|
||||||
|
admin.site.register(Position,PositionAdmin)
|
||||||
|
admin.site.register(OrgUnit,OrgUnitAdmin)
|
||||||
|
admin.site.register(Organization,OrgAdmin)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
__author__ = 'zhugl'
|
||||||
|
# created at 15-4-22
|
||||||
|
from django.apps.config import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class OrganConfig(AppConfig):
|
||||||
|
name = 'organ'
|
||||||
|
verbose_name = _('organization')
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.db import models
|
||||||
|
from django.db import connection
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import const
|
||||||
|
from common import generic
|
||||||
|
|
||||||
|
|
||||||
|
class Organization(generic.BO):
|
||||||
|
"""
|
||||||
|
组织单位
|
||||||
|
"""
|
||||||
|
index_weight = 1
|
||||||
|
code = models.CharField(_("organ code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
name = models.CharField(_("organ name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
|
||||||
|
tax_num = models.CharField(_("tax num"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
tax_address = models.CharField(_("tax address"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
tax_account = models.CharField(_("tax account"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||||
|
|
||||||
|
represent = models.CharField(_("representative "),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
address = models.CharField(_("address"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
zipcode = models.CharField(_("zipcode"),max_length=const.DB_CHAR_CODE_8,blank=True,null=True)
|
||||||
|
fax = models.CharField(_("fax"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
contacts = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
website = models.CharField(_("website"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
email = models.CharField(_("email"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
lic_code = models.CharField(_("license code"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
cer_code = models.CharField(_("certificate code"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
license = models.FileField(_("business license"),blank=True,null=True,upload_to='organ')
|
||||||
|
certificate = models.FileField(_("organization code certificate"),blank=True,null=True,upload_to='organ')
|
||||||
|
weight = models.IntegerField(_("weight"),default=9)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('organization')
|
||||||
|
verbose_name_plural = _('organization')
|
||||||
|
|
||||||
|
|
||||||
|
class OrgUnit(generic.BO):
|
||||||
|
"""
|
||||||
|
组织单元
|
||||||
|
"""
|
||||||
|
UNIT_LEVEL = (
|
||||||
|
(1,_('BRANCH')),
|
||||||
|
(2,_('DEPARTMENT')),
|
||||||
|
(3,_('OFFICE')),
|
||||||
|
(4,_('TEAM')),
|
||||||
|
(5,_('COMMITTEE'))
|
||||||
|
)
|
||||||
|
index_weight = 2
|
||||||
|
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||||
|
organization = models.ForeignKey(Organization,verbose_name = _('organization'),null=True,blank=True)
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_CODE_8,blank=True,null=True)
|
||||||
|
name = models.CharField(_("name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
unit_type = models.IntegerField(_("type"),choices=UNIT_LEVEL,default=2)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
virtual = models.BooleanField(_("is virtual"),default=False)
|
||||||
|
fax = models.CharField(_("fax"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
contacts = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
email = models.CharField(_("email"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
weight = models.IntegerField(_("weight"),default=99)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('org unit')
|
||||||
|
verbose_name_plural = _('org unit')
|
||||||
|
|
||||||
|
|
||||||
|
class Position(generic.BO):
|
||||||
|
"""
|
||||||
|
岗位
|
||||||
|
"""
|
||||||
|
SERIES = (
|
||||||
|
('A',_("Admin Position")),
|
||||||
|
('S',_("Sale Position")),
|
||||||
|
('T',_("Technology Position")),
|
||||||
|
('P',_("Produce Position")),
|
||||||
|
)
|
||||||
|
|
||||||
|
GRADE = (
|
||||||
|
('01', _("BASIC")),
|
||||||
|
('02', _("MEDIUM")),
|
||||||
|
('03', _("SENIOR")),
|
||||||
|
('04', _("PROFESSOR")),
|
||||||
|
('05', _("EXPERT")),
|
||||||
|
)
|
||||||
|
index_weight = 3
|
||||||
|
unit = models.ForeignKey(OrgUnit,verbose_name=_('org unit'))
|
||||||
|
organization = models.ForeignKey(Organization,verbose_name=_('organization'),null=True,blank=True)
|
||||||
|
parent = models.ForeignKey('self',verbose_name=_("parent"),null=True,blank=True)
|
||||||
|
code = models.CharField(_("position code"),max_length=const.DB_CHAR_CODE_8,blank=True,null=True)
|
||||||
|
name = models.CharField(_("position name"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
short = models.CharField(_("short name"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
pinyin = models.CharField(_("pinyin"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
series = models.CharField(_("position series"),max_length=1,default='A',choices=const.get_value_list('S014'))
|
||||||
|
grade = models.CharField(_("position grade"),max_length=const.DB_CHAR_CODE_2,default='01',choices=const.get_value_list('S015'))
|
||||||
|
virtual = models.BooleanField(_("is virtual"),default=False)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
description = models.TextField(_("position description"),blank=True,null=True)
|
||||||
|
qualification = models.TextField(_("qualification"),blank=True,null=True)
|
||||||
|
document = models.FileField(_("reference"),blank=True,null=True)
|
||||||
|
weight = models.IntegerField(_("weight"),default=99)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'%s %s' % (self.code,self.name)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('position')
|
||||||
|
verbose_name_plural = _('position')
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# created at 15-6-27
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# created at 15-6-30
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
|
||||||
|
|
||||||
|
class Operation(object):
|
||||||
|
APPROVE = 1
|
||||||
|
DENY = 3
|
||||||
|
TERMINATE = 4
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowAction(object):
|
||||||
|
|
||||||
|
name = ''
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
def action(self,request,obj,node_config,operation=Operation.APPROVE):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param request:
|
||||||
|
:param obj:
|
||||||
|
:param node_config:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TestAction(WorkflowAction):
|
||||||
|
name = 'action.test'
|
||||||
|
|
||||||
|
def action(self,request,obj,node_config,operation=Operation.APPROVE):
|
||||||
|
print 'this is a workflow test action'
|
||||||
|
print 'request user is %s,current node is %s'%(request.user,node_config)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowActionManager(object):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
actions = {}
|
||||||
|
registed = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if WorkflowActionManager.registed:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
WorkflowActionManager.register(TestAction)
|
||||||
|
WorkflowActionManager.registed = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls,action):
|
||||||
|
if cls.actions.get(action.name):
|
||||||
|
raise Exception('%s already exists,register failed'%action.name)
|
||||||
|
if issubclass(action,WorkflowAction):
|
||||||
|
WorkflowActionManager.actions[action.name] = action()
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# created at 15-6-30
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
from workflow.models import Node
|
||||||
|
|
||||||
|
|
||||||
|
class NextNodeHandler(object):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = ''
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
def handle(self,request,obj,node_config):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param request:
|
||||||
|
:param obj:
|
||||||
|
:param node_config:
|
||||||
|
:return:workflow.models.Node
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class TestHandler(NextNodeHandler):
|
||||||
|
name = 'project.budge.gt.10000'
|
||||||
|
description = '预算金额大于10000,由总经理审批'
|
||||||
|
|
||||||
|
def handle(self,request,obj,node_config):
|
||||||
|
budget = getattr(obj,'budget',None)
|
||||||
|
if budget and budget > 10000:
|
||||||
|
return Node.objects.filter(id=7).all()
|
||||||
|
|
||||||
|
|
||||||
|
class NextNodeManager(object):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
handlers = {}
|
||||||
|
registed = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if NextNodeManager.registed:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
NextNodeManager.register(TestHandler)
|
||||||
|
NextNodeManager.registed = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls,handler):
|
||||||
|
if cls.handlers.get(handler.name):
|
||||||
|
raise Exception('%s already exists,register failed'%handler.name)
|
||||||
|
if issubclass(handler,NextNodeHandler):
|
||||||
|
NextNodeManager.handlers[handler.name] = handler()
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# created at 15-6-30
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
|
||||||
|
|
||||||
|
class NextUserHandler(object):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = ''
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
def handle(self,request,obj,node_config):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param request:
|
||||||
|
:param obj:
|
||||||
|
:param node_config:
|
||||||
|
:return:django.contrib.auth.models.User[]
|
||||||
|
"""
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class UpPosition(NextUserHandler):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = 'up.position.user'
|
||||||
|
|
||||||
|
def handle(self,request,obj,node_config):
|
||||||
|
from basedata.models import Employee,Position
|
||||||
|
emp_query = Employee.objects.filter(user=request.user)
|
||||||
|
if emp_query.count()>0:
|
||||||
|
emp = emp_query.all()
|
||||||
|
parent = []
|
||||||
|
for e in emp:
|
||||||
|
if e.position and e.position.parent:
|
||||||
|
parent.append(e.position.parent)
|
||||||
|
# print emp
|
||||||
|
# print parent
|
||||||
|
query2 = Employee.objects.filter(position__in=parent).exclude(user=None)
|
||||||
|
return [x.user for x in query2.all()]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class NextUserManager(object):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
handlers = {}
|
||||||
|
registed = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if NextUserManager.registed:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
NextUserManager.register(UpPosition)
|
||||||
|
NextUserManager.registed = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls,handler):
|
||||||
|
if cls.handlers.get(handler.name):
|
||||||
|
raise Exception('%s already exists,register failed'%handler.name)
|
||||||
|
if issubclass(handler,NextUserHandler):
|
||||||
|
NextUserManager.handlers[handler.name] = handler()
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
# created at 15-6-27
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
import xlrd
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
from mis import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(object):
|
||||||
|
name = ''
|
||||||
|
|
||||||
|
def handle(self,obj,f):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OPSHandler(Handler):
|
||||||
|
"""
|
||||||
|
导入基础信息:部门,岗位,职员
|
||||||
|
"""
|
||||||
|
name = 'OPS'
|
||||||
|
|
||||||
|
def handle(self,obj,f):
|
||||||
|
if f and f.name.endswith('.xls'):
|
||||||
|
path = os.path.join(settings.MEDIA_ROOT,f.name)
|
||||||
|
workbook = xlrd.open_workbook(path)
|
||||||
|
for sheet in workbook.sheets():
|
||||||
|
if sheet.name == u'部门' or sheet.name == 'department':
|
||||||
|
self.department(obj,sheet)
|
||||||
|
elif sheet.name == u'岗位' or sheet.name == 'position':
|
||||||
|
self.position(obj,sheet)
|
||||||
|
elif sheet.name == u'职员' or sheet.name == 'employee':
|
||||||
|
self.stuff(obj,sheet)
|
||||||
|
|
||||||
|
def department(self,obj,sheet):
|
||||||
|
from organ.models import OrgUnit
|
||||||
|
row_count = sheet.nrows
|
||||||
|
if obj.is_clear:
|
||||||
|
OrgUnit.objects.update(end=datetime.date.today())
|
||||||
|
for row_index in range(1,row_count):
|
||||||
|
row = sheet.row_values(row_index)
|
||||||
|
weight = 99
|
||||||
|
if row[6]:
|
||||||
|
weight = row[6]
|
||||||
|
OrgUnit.objects.create(code=row[0],name=row[1],short=row[2],pinyin=row[3],begin=datetime.date.today(),
|
||||||
|
end=datetime.date(9999,12,31),weight=weight)
|
||||||
|
for row_index in range(1,row_count):
|
||||||
|
row = sheet.row_values(row_index)
|
||||||
|
if len(row[4]) > 0:
|
||||||
|
try:
|
||||||
|
parent = OrgUnit.objects.get(code=row[4])
|
||||||
|
OrgUnit.objects.filter(code=row[0]).update(parent=parent)
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def position(self,obj,sheet):
|
||||||
|
from organ.models import Position,OrgUnit
|
||||||
|
row_count = sheet.nrows
|
||||||
|
if obj.is_clear:
|
||||||
|
Position.objects.update(end=datetime.date.today())
|
||||||
|
for row_index in range(1,row_count):
|
||||||
|
row = sheet.row_values(row_index)
|
||||||
|
depart = None
|
||||||
|
if len(row[4]) > 0:
|
||||||
|
try:
|
||||||
|
depart = OrgUnit.objects.filter(code=row[4],end__gt=datetime.date.today()).all()[0]
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
weight = 99
|
||||||
|
if row[6]:
|
||||||
|
weight = row[6]
|
||||||
|
Position.objects.create(code=row[0],name=row[1],unit=depart,begin=datetime.date.today(),
|
||||||
|
end=datetime.date(9999,12,31),weight=weight)
|
||||||
|
for row_index in range(1,row_count):
|
||||||
|
row = sheet.row_values(row_index)
|
||||||
|
if len(row[2]) > 0:
|
||||||
|
try:
|
||||||
|
parent = Position.objects.filter(code=row[2],end__gt=datetime.date.today()).all()[0]
|
||||||
|
Position.objects.filter(code=row[0]).update(parent=parent)
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stuff(self,obj,sheet):
|
||||||
|
from organ.models import Position
|
||||||
|
from basedata.models import Employee
|
||||||
|
from django.contrib.auth.models import User,Group
|
||||||
|
row_count = sheet.nrows
|
||||||
|
try:
|
||||||
|
group = Group.objects.get_by_natural_key(u'职员')
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
for row_index in range(1,row_count):
|
||||||
|
row = sheet.row_values(row_index)
|
||||||
|
position = Position.objects.filter(code=row[8],end__gt=datetime.date.today()).all()[0]
|
||||||
|
username = row[10]
|
||||||
|
email = row[11]
|
||||||
|
password = row[4][-6:]
|
||||||
|
if position is None:
|
||||||
|
raise Exception(u'职员%s-%s未分配岗位,或者您选择的岗位已失效,不可被引用'%(row[0],row[1]))
|
||||||
|
try:
|
||||||
|
employee = Employee.objects.get(code=row[0])
|
||||||
|
if employee.position.code == row[8]:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
employee.position = position
|
||||||
|
employee.save()
|
||||||
|
except Exception,e:
|
||||||
|
employee = Employee.objects.create(code=row[0],name=row[1],pinyin=row[2],gender=row[3],idcard=row[4],
|
||||||
|
birthday=row[5],workday=row[6],startday=row[7],position=position)
|
||||||
|
if username:
|
||||||
|
try:
|
||||||
|
user = User.objects.get_by_natural_key(username)
|
||||||
|
except Exception,e:
|
||||||
|
user = User.objects.create_user(username=username,password=password)
|
||||||
|
user.is_staff = True
|
||||||
|
user.is_active = True
|
||||||
|
user.first_name = row[1]
|
||||||
|
if email:
|
||||||
|
user.email = email
|
||||||
|
if group:
|
||||||
|
user.groups.add(group)
|
||||||
|
user.save()
|
||||||
|
employee.user = user
|
||||||
|
employee.save()
|
||||||
|
|
||||||
|
|
||||||
|
class UserHandler(Handler):
|
||||||
|
"""
|
||||||
|
基础数据导入:用户
|
||||||
|
"""
|
||||||
|
name = 'admin.user'
|
||||||
|
|
||||||
|
def handle(self,obj,f):
|
||||||
|
from django.contrib.auth.models import User,Group
|
||||||
|
if f and f.name.endswith('.xls'):
|
||||||
|
path = os.path.join(settings.MEDIA_ROOT,f.name)
|
||||||
|
workbook = xlrd.open_workbook(path)
|
||||||
|
sheet = workbook.sheet_by_index(0)
|
||||||
|
row_count = sheet.nrows
|
||||||
|
try:
|
||||||
|
group = Group.objects.get_by_natural_key(u'职员')
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for row_index in range(2,row_count):
|
||||||
|
row = sheet.row_values(row_index)
|
||||||
|
username = row[0]
|
||||||
|
password = row[1]
|
||||||
|
last_name = row[2]
|
||||||
|
first_name = row[3]
|
||||||
|
email = row[4]
|
||||||
|
if username == '':
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
user = User.objects.get_by_natural_key(username)
|
||||||
|
except Exception,e:
|
||||||
|
user = User.objects.create_user(username=username,password=password,email=email)
|
||||||
|
user.is_staff = True
|
||||||
|
user.is_active = True
|
||||||
|
user.last_name = last_name
|
||||||
|
user.first_name = first_name
|
||||||
|
if group:
|
||||||
|
user.groups.add(group)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
|
class ExcelManager(object):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
handlers = {}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
ExcelManager.register(OPSHandler)
|
||||||
|
ExcelManager.register(UserHandler)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls,handler):
|
||||||
|
if cls.handlers.get(handler.name):
|
||||||
|
raise Exception('%s already exists,register failed'%handler.name)
|
||||||
|
if issubclass(handler,Handler):
|
||||||
|
ExcelManager.handlers[handler.name] = handler()
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = "purchase.apps.MyAppConfig"
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from common import generic
|
||||||
|
from purchase.models import PurchaseOrder,POItem,Invoice,Payment
|
||||||
|
from basedata.models import Partner,BankAccount
|
||||||
|
|
||||||
|
|
||||||
|
class POItemInline(admin.TabularInline):
|
||||||
|
model = POItem
|
||||||
|
fields = ('material', 'measure','price', 'cnt', 'tax', 'discount_price')
|
||||||
|
raw_id_fields = ['material']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderAdmin(generic.BOAdmin):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
CODE_PREFIX = 'CG'
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
list_display = ['code','title','order_date','partner','amount','discount_amount','payed_amount','invoice_amount','status']
|
||||||
|
list_display_links = ['code','title']
|
||||||
|
raw_id_fields = ['partner']
|
||||||
|
fields = (
|
||||||
|
('code','partner'),('order_date','arrive_date'),
|
||||||
|
('title','status',),('description',),('amount','discount_amount'),('attach',),
|
||||||
|
)
|
||||||
|
readonly_fields = ['status','amount']
|
||||||
|
inlines = [POItemInline]
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
if object_id:
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
obj = PurchaseOrder.objects.get(id=object_id)
|
||||||
|
if obj.status == '99':
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
return super(PurchaseOrderAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
def get_changeform_initial_data(self, request):
|
||||||
|
import datetime
|
||||||
|
begin = datetime.date.today()
|
||||||
|
end = begin + datetime.timedelta(30)
|
||||||
|
return {'order_date':begin,'arrive_date':end}
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseItemAdmin(generic.BOAdmin):
|
||||||
|
list_display = ['po','vender','material','cnt','price','tax']
|
||||||
|
readonly_fields = ['po','material','cnt','price','tax']
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
return POItem.objects.filter(left_cnt__gt=0)
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceAdmin(generic.BOAdmin):
|
||||||
|
list_display = ['code','number','vo_date','po','partner','po_amount','vo_amount']
|
||||||
|
readonly_fields = ['partner','po_amount']
|
||||||
|
raw_id_fields = ['po']
|
||||||
|
search_fields = ['po__code','partner__name']
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
fields = (
|
||||||
|
('vo_date',),('po',),('po_amount','partner',),('code',),('number',),('vo_amount',),('file',)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'PY'
|
||||||
|
CODE_NUMBER_WIDTH = 4
|
||||||
|
list_display = ['code','py_date','po','partner','po_amount','py_amount']
|
||||||
|
readonly_fields = ['partner','po_amount']
|
||||||
|
raw_id_fields = ['po']
|
||||||
|
search_fields = ['po__code','partner__name']
|
||||||
|
fields = (
|
||||||
|
('py_date','code',),('po',),('partner','po_amount',),('py_amount',),('bank',),('response_code'),('memo',)
|
||||||
|
)
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||||
|
if db_field.name == 'bank':
|
||||||
|
kwargs['queryset'] = BankAccount.objects.exclude(org__exact=None)
|
||||||
|
return super(PaymentAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||||
|
|
||||||
|
admin.site.register(PurchaseOrder,PurchaseOrderAdmin)
|
||||||
|
admin.site.register(POItem,PurchaseItemAdmin)
|
||||||
|
admin.site.register(Invoice,InvoiceAdmin)
|
||||||
|
admin.site.register(Payment,PaymentAdmin)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# created at 15-5-23
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class MyAppConfig(AppConfig):
|
||||||
|
name = 'purchase'
|
||||||
|
verbose_name = _("purchase manage")
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import xlrd
|
||||||
|
import decimal
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models.aggregates import Sum
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.text import force_text
|
||||||
|
from mis import settings
|
||||||
|
from common import generic
|
||||||
|
from common import const
|
||||||
|
from selfhelp.models import WOItem
|
||||||
|
from basedata.models import Material,Organization,Partner,Measure,BankAccount
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrder(generic.BO):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
STATUS = (
|
||||||
|
('0', _("NEW")),
|
||||||
|
('1', _("IN PROGRESS")),
|
||||||
|
('4', _("DROP")),
|
||||||
|
('9', _("APPROVED")),
|
||||||
|
('99', _("ALREADY STOCK IN")),
|
||||||
|
)
|
||||||
|
index_weight = 1
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
partner = models.ForeignKey(Partner,verbose_name=_("partner"),limit_choices_to={"partner_type":"S"})
|
||||||
|
order_date = models.DateField(_("order date"))
|
||||||
|
arrive_date = models.DateField(_("arrive date"))
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
description = models.TextField(_("description"),blank=True,null=True)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||||
|
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||||
|
amount = models.DecimalField(_("money amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||||
|
discount_amount = models.DecimalField(_("discount amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||||
|
entry_status = models.BooleanField(_("entry status"),default=0)
|
||||||
|
entry_time = models.DateTimeField(_("entry time"),blank=True,null=True)
|
||||||
|
attach = models.FileField(_('attach'),blank=True,null=True,help_text=u'您可导入采购明细,模板请参考文档FD0008')
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'%s %s' % (self.code,self.title)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
super(PurchaseOrder,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
if self.discount_amount > 0:
|
||||||
|
sql = 'UPDATE purchase_poitem a SET a.discount_price = ' \
|
||||||
|
'a.price-((SELECT discount_amount/amount FROM purchase_purchaseorder WHERE id=%s)*a.amount/a.cnt) WHERE a.po_id = %s'
|
||||||
|
params = [self.id,self.id]
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
item_count = POItem.objects.filter(po=self).count()
|
||||||
|
if self.attach and item_count == 0:
|
||||||
|
path = os.path.join(settings.MEDIA_ROOT,self.attach.name)
|
||||||
|
workbook = xlrd.open_workbook(path)
|
||||||
|
sheet = workbook.sheet_by_index(0)
|
||||||
|
row_count = sheet.nrows
|
||||||
|
with transaction.atomic():
|
||||||
|
total_amount = decimal.Decimal(0)
|
||||||
|
for row_index in range(row_count):
|
||||||
|
row = sheet.row_values(row_index)
|
||||||
|
if row_index == 0:
|
||||||
|
doc_type = row[1]
|
||||||
|
if doc_type.startswith('0'):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
elif row_index < 3:
|
||||||
|
continue
|
||||||
|
|
||||||
|
material = None
|
||||||
|
measure = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
measure = Measure.objects.get(code=row[4])
|
||||||
|
except Exception,e:
|
||||||
|
measure = Measure.objects.create(code=row[4],name=force_text(row[5]))
|
||||||
|
|
||||||
|
try:
|
||||||
|
material = Material.objects.get(code=row[0])
|
||||||
|
except Exception,e:
|
||||||
|
material = Material(code=row[0],name=force_text(row[1]),spec=force_text(row[2]))
|
||||||
|
material.purchase_price = row[6]
|
||||||
|
material.save()
|
||||||
|
amount = decimal.Decimal(row[6])*decimal.Decimal(row[7])
|
||||||
|
POItem.objects.create(po=self,material=material,measure=measure,cnt=row[7],price=row[6],amount=amount)
|
||||||
|
total_amount += amount
|
||||||
|
sql = 'update purchase_purchaseorder set amount = %s where id=%s'
|
||||||
|
params = [total_amount,self.id]
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
def invoice_amount(self):
|
||||||
|
total = Invoice.objects.filter(po=self).aggregate(Sum('vo_amount')).get('vo_amount__sum') or 0.00
|
||||||
|
return total
|
||||||
|
|
||||||
|
def payed_amount(self):
|
||||||
|
return Payment.objects.filter(po=self).aggregate(Sum('py_amount')).get('py_amount__sum') or 0.00
|
||||||
|
|
||||||
|
invoice_amount.short_description = _('invoice amount')
|
||||||
|
payed_amount.short_description = _('pay amount')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("purchase order")
|
||||||
|
verbose_name_plural = _("purchase orders")
|
||||||
|
|
||||||
|
|
||||||
|
class POItem(models.Model):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
index_weight = 2
|
||||||
|
po = models.ForeignKey(PurchaseOrder,verbose_name=_("purchase order"))
|
||||||
|
material = models.ForeignKey(Material,verbose_name=_("material"),limit_choices_to={"is_virtual":"0"})
|
||||||
|
measure = models.ForeignKey(Measure,verbose_name=_("measure"),blank=True,null=True)
|
||||||
|
price = models.DecimalField(_("price"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||||
|
cnt = models.DecimalField(_("count"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||||
|
discount_price = models.DecimalField(_("discount price"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||||
|
amount = models.DecimalField(_("money of amount"),max_digits=12,decimal_places=2,blank=True,null=True)
|
||||||
|
discount_amount = models.DecimalField(_("discount amount"),max_digits=12,decimal_places=2,blank=True,null=True)
|
||||||
|
tax = models.CharField(_("tax rate"),max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S052'),default='0.00')
|
||||||
|
woitem = models.ForeignKey(WOItem,verbose_name=_("wo item"),blank=True,null=True)
|
||||||
|
is_in_stock = models.BooleanField(_("is in stock"),default=0)
|
||||||
|
in_stock_time = models.DateTimeField(_("execute time"),blank=True,null=True)
|
||||||
|
entry_cnt = models.DecimalField(_("entry count"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||||
|
left_cnt = models.DecimalField(_("left count"),max_digits=12,decimal_places=4,blank=True,null=True)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if self.price and self.cnt:
|
||||||
|
money = self.price * self.cnt
|
||||||
|
self.amount = money
|
||||||
|
|
||||||
|
if self.measure is None and self.material and self.material.measure.count() > 0:
|
||||||
|
self.measure = self.material.measure.all()[0]
|
||||||
|
|
||||||
|
if self.is_in_stock:
|
||||||
|
self.left_cnt -= self.entry_cnt
|
||||||
|
else:
|
||||||
|
self.left_cnt = self.cnt
|
||||||
|
super(POItem,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
self.material.purchase_price = self.price
|
||||||
|
self.material.save()
|
||||||
|
sql = 'UPDATE purchase_purchaseorder SET amount = (SELECT SUM(a.price*a.cnt) AS amount FROM ' \
|
||||||
|
'purchase_poitem a WHERE a.po_id = %s) WHERE id = %s'
|
||||||
|
params = [self.po.id,self.po.id]
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
def vender(self):
|
||||||
|
return u'%s' % (self.po.partner)
|
||||||
|
|
||||||
|
vender.short_description = _("partner")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("po item")
|
||||||
|
verbose_name_plural = _("po item")
|
||||||
|
|
||||||
|
|
||||||
|
class Invoice(generic.BO):
|
||||||
|
"""
|
||||||
|
采购发票
|
||||||
|
"""
|
||||||
|
index_weight = 4
|
||||||
|
vo_date = models.DateField(_("invoice date"),blank=True,null=True,default=datetime.date.today)
|
||||||
|
code = models.CharField(_("invoice code"),max_length=const.DB_CHAR_NAME_20)
|
||||||
|
number = models.CharField(_("invoice number"),max_length=const.DB_CHAR_NAME_20)
|
||||||
|
po = models.ForeignKey(PurchaseOrder,verbose_name=_("purchase order"))
|
||||||
|
partner = models.ForeignKey(Partner,verbose_name=_("partner"),blank=True,null=True)
|
||||||
|
po_amount = models.DecimalField(_("po amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
vo_amount = models.DecimalField(_("invoice amount"),max_digits=14,decimal_places=4)
|
||||||
|
file = models.FileField(_("invoice file"),upload_to='invoice',blank=True,null=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s %s" % (self.code,self.partner)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if self.po:
|
||||||
|
self.partner = self.po.partner
|
||||||
|
self.po_amount = self.po.amount
|
||||||
|
super(Invoice,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Invoice")
|
||||||
|
verbose_name_plural = _("Invoice")
|
||||||
|
|
||||||
|
|
||||||
|
class Payment(generic.BO):
|
||||||
|
"""
|
||||||
|
采购付款
|
||||||
|
"""
|
||||||
|
index_weight = 3
|
||||||
|
py_date = models.DateField(_("pay date"),blank=True,null=True,default=datetime.date.today)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
code = models.CharField(_("pay code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
po = models.ForeignKey(PurchaseOrder,verbose_name=_("purchase order"))
|
||||||
|
partner = models.ForeignKey(Partner,verbose_name=_("partner"),blank=True,null=True)
|
||||||
|
po_amount = models.DecimalField(_("po amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
py_amount = models.DecimalField(_("pay amount"),max_digits=14,decimal_places=4)
|
||||||
|
bank = models.ForeignKey(BankAccount,verbose_name=_("bank account"),blank=True,null=True)
|
||||||
|
response_code = models.CharField(_("response code"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||||
|
memo = models.TextField(_("memo"),blank=True,null=True)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if self.po:
|
||||||
|
self.partner = self.po.partner
|
||||||
|
self.po_amount = self.po.amount
|
||||||
|
super(Payment,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s %s" % (self.code,self.partner)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Payment")
|
||||||
|
verbose_name_plural = _("Payment")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = "sale.apps.MyAppConfig"
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from common import generic
|
||||||
|
from sale.models import SaleOrder,SaleItem,PaymentCollection,OfferSheet,OfferItem
|
||||||
|
from basedata.models import Measure,BankAccount
|
||||||
|
from common import generic
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class SaleItemInline(admin.TabularInline):
|
||||||
|
model = SaleItem
|
||||||
|
fields = ('material','measure','sale_price','discount_price','cnt','tax')
|
||||||
|
raw_id_fields = ['material']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
|
||||||
|
class OfferItemInline(admin.TabularInline):
|
||||||
|
model = OfferItem
|
||||||
|
fields = ('material','brand','measure','cost_price','sale_price','discount_price','cnt','tax')
|
||||||
|
raw_id_fields = ['material']
|
||||||
|
readonly_fields = ['brand']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
|
||||||
|
class SaleOrderAdmin(generic.BOAdmin):
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
CODE_PREFIX = 'SO'
|
||||||
|
inlines = [SaleItemInline]
|
||||||
|
list_display = ['code','title','order_date','partner','amount','collection_amount']
|
||||||
|
list_display_links = ['code','title']
|
||||||
|
raw_id_fields = ['partner','user','org']
|
||||||
|
fields = (
|
||||||
|
('code','org',),('title','invoice_type',),('partner','user',),('order_date','deliver_date',),
|
||||||
|
('contact','phone',),('deliver_address','fax',),('description',),('amount','discount_amount','status')
|
||||||
|
)
|
||||||
|
readonly_fields = ['amount','status']
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if obj:
|
||||||
|
obj.user = request.user
|
||||||
|
super(SaleOrderAdmin,self).save_model(request,obj,form,change)
|
||||||
|
|
||||||
|
def get_changeform_initial_data(self, request):
|
||||||
|
today = datetime.datetime.today()
|
||||||
|
deadline = today+datetime.timedelta(days=30)
|
||||||
|
return {'order_date':today,'deliver_date':deadline}
|
||||||
|
|
||||||
|
|
||||||
|
class OfferSheetAdmin(generic.BOAdmin):
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
CODE_PREFIX = 'BJ'
|
||||||
|
inlines = [OfferItemInline]
|
||||||
|
list_display = ['code','title','offer_date','partner','amount']
|
||||||
|
list_display_links = ['code','title']
|
||||||
|
raw_id_fields = ['partner','user','org']
|
||||||
|
fields = (
|
||||||
|
('code','org',),('partner','user',),('offer_date','deliver_date',),('title',),
|
||||||
|
('description',),('amount','discount_amount',),('attach'),
|
||||||
|
)
|
||||||
|
readonly_fields = ['amount']
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if obj:
|
||||||
|
obj.user = request.user
|
||||||
|
super(OfferSheetAdmin,self).save_model(request,obj,form,change)
|
||||||
|
|
||||||
|
def get_changeform_initial_data(self, request):
|
||||||
|
today = datetime.datetime.today()
|
||||||
|
deadline = today+datetime.timedelta(days=30)
|
||||||
|
return {'offer_date':today,'deliver_date':deadline}
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentCollectionAdmin(generic.BOAdmin):
|
||||||
|
CODE_NUMBER_WIDTH = 4
|
||||||
|
CODE_PREFIX = 'CP'
|
||||||
|
list_display = ['code','so','partner','order_amount','collection_date','collection_amount']
|
||||||
|
fields = (
|
||||||
|
('code',),('collection_date',),('so',),('partner',),('order_amount','collection_amount',),('bank',),
|
||||||
|
('memo',)
|
||||||
|
)
|
||||||
|
raw_id_fields = ['so','partner']
|
||||||
|
readonly_fields = ['order_amount']
|
||||||
|
list_display_links = ['code','so']
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||||
|
if db_field.name=='bank':
|
||||||
|
kwargs['queryset'] = BankAccount.objects.exclude(org__exact=None)
|
||||||
|
return super(PaymentCollectionAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||||
|
|
||||||
|
admin.site.register(SaleOrder,SaleOrderAdmin)
|
||||||
|
admin.site.register(PaymentCollection,PaymentCollectionAdmin)
|
||||||
|
admin.site.register(OfferSheet,OfferSheetAdmin)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# created at 15-5-23
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class MyAppConfig(AppConfig):
|
||||||
|
name = 'sale'
|
||||||
|
verbose_name = _("sale management")
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import decimal
|
||||||
|
import datetime
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import xlrd
|
||||||
|
from mis import settings
|
||||||
|
from django.db import models
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db.models.aggregates import Sum
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.text import force_text
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import generic
|
||||||
|
from common import const
|
||||||
|
from basedata.models import Material,Project,Partner,Organization,Measure,BankAccount
|
||||||
|
|
||||||
|
|
||||||
|
class SaleOrder(generic.BO):
|
||||||
|
"""
|
||||||
|
销售订单
|
||||||
|
"""
|
||||||
|
STATUS = (
|
||||||
|
('0', _("NEW")),
|
||||||
|
('1', _("IN PROGRESS")),
|
||||||
|
('4', _("DROP")),
|
||||||
|
('9', _("APPROVED")),
|
||||||
|
('99', _("ALREADY STOCK OUT")),
|
||||||
|
)
|
||||||
|
index_weight = 2
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
partner = models.ForeignKey(Partner,verbose_name=_("partner"),limit_choices_to={"partner_type":"C"})
|
||||||
|
order_date = models.DateField(_("order date"))
|
||||||
|
deliver_date = models.DateField(_("deliver date"))
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
description = models.TextField(_("memo"),blank=True,null=True)
|
||||||
|
contact = models.CharField(_("contacts"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
phone = models.CharField(_("phone"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
fax = models.CharField(_("fax"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
deliver_address = models.CharField(_("deliver address"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
invoice_type = models.CharField(_("invoice type"),max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S053'),default='10')
|
||||||
|
|
||||||
|
amount = models.DecimalField(_("money amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||||
|
discount_amount = models.DecimalField(_("discount amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("sales man"),blank=True,null=True)
|
||||||
|
status = models.CharField(_("status"),max_length=const.DB_CHAR_CODE_2,default='0',choices=STATUS)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'%s %s' % (self.code,self.title)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
super(SaleOrder,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
if self.discount_amount > 0:
|
||||||
|
sql = 'UPDATE sale_saleitem a SET a.discount_price = a.sale_price - ' \
|
||||||
|
'((SELECT discount_amount/amount FROM sale_saleorder WHERE id = %s) * (a.sale_price*a.cnt)/a.cnt) WHERE a.master_id = %s'
|
||||||
|
params = [self.id,self.id]
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
def collection_amount(self):
|
||||||
|
return PaymentCollection.objects.filter(so=self).aggregate(Sum('collection_amount')).get('collection_amount__sum') or 0.00
|
||||||
|
|
||||||
|
collection_amount.short_description = _('collection amount')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('sale order')
|
||||||
|
verbose_name_plural = _('sale order')
|
||||||
|
|
||||||
|
|
||||||
|
class SaleItem(models.Model):
|
||||||
|
"""
|
||||||
|
订单明细
|
||||||
|
"""
|
||||||
|
master = models.ForeignKey(SaleOrder)
|
||||||
|
material = models.ForeignKey(Material,verbose_name=_("material"),limit_choices_to={"is_virtual":"0",'can_sale':'1'},blank=True,null=True)
|
||||||
|
measure = models.ForeignKey(Measure,verbose_name=_("measure"),blank=True,null=True)
|
||||||
|
cnt = models.DecimalField(_("count"),max_digits=14,decimal_places=4)
|
||||||
|
stock_price = models.DecimalField(_("stock price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
sale_price = models.DecimalField(_("sale price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
discount_price = models.DecimalField(_("discount price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
tax = models.CharField(_("tax rate"),max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S052'),default='0.00')
|
||||||
|
create_time = models.DateTimeField(_("create time"),auto_now_add=True)
|
||||||
|
status = models.BooleanField(_("executed"),default=0)
|
||||||
|
event_time = models.DateTimeField(_("event time"),blank=True,null=True)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if self.material and self.material.measure.count() > 0:
|
||||||
|
self.measure = self.material.measure.all()[0]
|
||||||
|
if self.material.sale_price or self.material.stock_price:
|
||||||
|
self.sale_price = self.material.sale_price or self.material.stock_price*decimal.Decimal(1.17)/decimal.Decimal(0.6)
|
||||||
|
super(SaleItem,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
sql = 'update sale_saleorder set amount = (select sum(sale_price*cnt) from sale_saleitem where master_id=%s) where id=%s'
|
||||||
|
params = [self.master.id,self.master.id]
|
||||||
|
# print sql % (self.master.id,self.master.id)
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('order detail')
|
||||||
|
verbose_name_plural = _('order detail')
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentCollection(generic.BO):
|
||||||
|
"""
|
||||||
|
销售回款
|
||||||
|
"""
|
||||||
|
index_weight = 3
|
||||||
|
collection_date = models.DateField(_("collection date"),blank=True,null=True,default=datetime.date.today)
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
code = models.CharField(_("collection code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
so = models.ForeignKey(SaleOrder,verbose_name=_("sale order"))
|
||||||
|
partner = models.ForeignKey(Partner,verbose_name=_("partner"),blank=True,null=True)
|
||||||
|
order_amount = models.DecimalField(_("order amount"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
collection_amount = models.DecimalField(_("collection amount"),max_digits=14,decimal_places=4)
|
||||||
|
bank = models.ForeignKey(BankAccount,verbose_name=_("bank account"),blank=True,null=True)
|
||||||
|
memo = models.TextField(_("memo"),blank=True,null=True)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'%s %s'%(self.code,self.so)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if self.so:
|
||||||
|
self.partner = self.so.partner
|
||||||
|
self.order_amount = self.so.amount
|
||||||
|
if self.so.discount_amount > 0:
|
||||||
|
self.order_amount -= self.so.discount_amount
|
||||||
|
super(PaymentCollection,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Payment Collection')
|
||||||
|
verbose_name_plural = _('Payment Collection')
|
||||||
|
|
||||||
|
|
||||||
|
class OfferSheet(generic.BO):
|
||||||
|
"""
|
||||||
|
报价单
|
||||||
|
"""
|
||||||
|
index_weight = 1
|
||||||
|
STATUS = (
|
||||||
|
('0', _("NEW")),
|
||||||
|
('1', _("IN PROGRESS")),
|
||||||
|
('4', _("DROP")),
|
||||||
|
('9', _("APPROVED")),
|
||||||
|
)
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
partner = models.ForeignKey(Partner,verbose_name=_("partner"),limit_choices_to={"partner_type":"C"})
|
||||||
|
offer_date = models.DateField(_("offer date"))
|
||||||
|
deliver_date = models.DateField(_("deliver date"))
|
||||||
|
org = models.ForeignKey(Organization,verbose_name=_("organization"),blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
description = models.TextField(_("memo"),blank=True,null=True)
|
||||||
|
|
||||||
|
amount = models.DecimalField(_("money amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||||
|
discount_amount = models.DecimalField(_("discount amount"),max_digits=12,decimal_places=2,blank=True,null=True,default=0.00)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("offer man"),blank=True,null=True)
|
||||||
|
|
||||||
|
attach = models.FileField(_('offer sheet file'),blank=True,null=True,upload_to='offer sheet',help_text=u'您可导入报价明细,模板请参考文档FD0006')
|
||||||
|
status = models.BooleanField(_("executed"),default=0)
|
||||||
|
event_time = models.DateTimeField(_("event time"),blank=True,null=True)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
import decimal
|
||||||
|
super(OfferSheet,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
if self.discount_amount > 0:
|
||||||
|
sql = 'UPDATE sale_offeritem a SET a.discount_price = a.sale_price - ' \
|
||||||
|
'((SELECT discount_amount/amount FROM sale_offersheet WHERE id = %s) * (a.sale_price*a.cnt)/a.cnt) WHERE a.master_id = %s'
|
||||||
|
params = [self.id,self.id]
|
||||||
|
# print sql % (self.id,self.id)
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
item_count = OfferItem.objects.filter(master=self).count()
|
||||||
|
if self.attach and item_count == 0:
|
||||||
|
path = os.path.join(settings.MEDIA_ROOT,self.attach.name)
|
||||||
|
workbook = xlrd.open_workbook(path)
|
||||||
|
sheet = workbook.sheet_by_index(0)
|
||||||
|
row_count = sheet.nrows
|
||||||
|
with transaction.atomic():
|
||||||
|
total_amount = decimal.Decimal(0)
|
||||||
|
for row_index in range(row_count):
|
||||||
|
row = sheet.row_values(row_index)
|
||||||
|
if row_index == 0:
|
||||||
|
doc_type = row[1]
|
||||||
|
if doc_type.startswith('0'):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
elif row_index < 3:
|
||||||
|
continue
|
||||||
|
|
||||||
|
material = None
|
||||||
|
measure = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
measure = Measure.objects.get(code=row[4])
|
||||||
|
except Exception,e:
|
||||||
|
measure = Measure.objects.create(code=row[4],name=force_text(row[5]))
|
||||||
|
|
||||||
|
try:
|
||||||
|
material = Material.objects.get(code=row[0])
|
||||||
|
except Exception,e:
|
||||||
|
material = Material(code=row[0],name=force_text(row[1]),spec=force_text(row[2]))
|
||||||
|
material.sale_price = row[6]
|
||||||
|
material.purchase_price = row[7]
|
||||||
|
material.save()
|
||||||
|
OfferItem.objects.create(master=self,material=material,measure=measure,cnt=row[8],brand=force_text(row[3]),
|
||||||
|
cost_price=row[7],sale_price=row[6])
|
||||||
|
total_amount += decimal.Decimal(row[6])*decimal.Decimal(row[8])
|
||||||
|
sql = 'update sale_offersheet set amount = %s where id=%s'
|
||||||
|
params = [total_amount,self.id]
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('offer sheet')
|
||||||
|
verbose_name_plural = _('offer sheet')
|
||||||
|
|
||||||
|
|
||||||
|
class OfferItem(models.Model):
|
||||||
|
"""
|
||||||
|
订单明细
|
||||||
|
"""
|
||||||
|
master = models.ForeignKey(OfferSheet)
|
||||||
|
material = models.ForeignKey(Material,verbose_name=_("material"),limit_choices_to={"is_virtual":"0",'can_sale':'1'},blank=True,null=True)
|
||||||
|
measure = models.ForeignKey(Measure,verbose_name=_("measure"),blank=True,null=True)
|
||||||
|
cnt = models.DecimalField(_("count"),max_digits=14,decimal_places=4)
|
||||||
|
brand = models.CharField(_('brand'),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
cost_price = models.DecimalField(_("cost price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
stock_price = models.DecimalField(_("stock price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
sale_price = models.DecimalField(_("sale price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
discount_price = models.DecimalField(_("discount price"),max_digits=14,decimal_places=4,blank=True,null=True)
|
||||||
|
tax = models.CharField(_("tax rate"),max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S052'),default='0.00')
|
||||||
|
create_time = models.DateTimeField(_("create time"),auto_now_add=True)
|
||||||
|
status = models.BooleanField(_("executed"),default=0)
|
||||||
|
event_time = models.DateTimeField(_("event time"),blank=True,null=True)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if self.material and self.material.measure.count() > 0:
|
||||||
|
self.measure = self.material.measure.all()[0]
|
||||||
|
if self.material.sale_price or self.material.stock_price:
|
||||||
|
self.sale_price = self.material.sale_price or self.material.stock_price*decimal.Decimal(1.17)/decimal.Decimal(0.6)
|
||||||
|
super(OfferItem,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
sql = 'update sale_offersheet set amount = (select sum(sale_price*cnt) from sale_offeritem where master_id=%s) where id=%s'
|
||||||
|
params = [self.master.id,self.master.id]
|
||||||
|
# print sql % (self.master.id,self.master.id)
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('offer detail')
|
||||||
|
verbose_name_plural = _('offer detail')
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = "selfhelp.apps.MyAppConfig"
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
# coding = utf-8
|
||||||
|
import datetime
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.db.models.aggregates import Sum
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import generic
|
||||||
|
from common import const
|
||||||
|
from basedata.models import Material,ExtraParam,Employee,Position
|
||||||
|
from selfhelp.models import WorkOrder,WOExtraValue,WOItem,Reimbursement,ReimbursementItem,Loan,Enroll,Feedback,Activity
|
||||||
|
|
||||||
|
|
||||||
|
class ParamValueInline(admin.TabularInline):
|
||||||
|
model = WOExtraValue
|
||||||
|
fields = ('param_name','param_value')
|
||||||
|
readonly_fields = ['param_name']
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||||
|
|
||||||
|
if db_field.name == 'param_name':
|
||||||
|
app_info = generic.get_app_model_info_from_request(request)
|
||||||
|
instance = app_info['obj']
|
||||||
|
if instance:
|
||||||
|
kwargs['queryset'] = ExtraParam.objects.filter(material=instance.service)
|
||||||
|
|
||||||
|
return super(ParamValueInline,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class ItemInline(admin.TabularInline):
|
||||||
|
model = WOItem
|
||||||
|
raw_id_fields = ['material']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class WorkOrderAdmin(generic.BOAdmin):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
CODE_PREFIX = 'WO'
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
list_display = ['code','begin','title','classification','business_domain','status']
|
||||||
|
list_display_links = ['code','title']
|
||||||
|
exclude = ['creator','modifier','creation','modification']
|
||||||
|
search_fields = ['code','title']
|
||||||
|
list_filter = ['classification','service','status']
|
||||||
|
fields = (
|
||||||
|
('begin','end',),
|
||||||
|
('code','refer',),('classification','business_domain',),
|
||||||
|
('service','project',),
|
||||||
|
('title','status',),('description',),('attach',),('detail',)
|
||||||
|
)
|
||||||
|
readonly_fields = ['status']
|
||||||
|
raw_id_fields = ['service','project','refer']
|
||||||
|
inlines = [ItemInline,ParamValueInline]
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if obj.user is None:
|
||||||
|
obj.user = request.user
|
||||||
|
super(WorkOrderAdmin,self).save_model(request,obj,form,change)
|
||||||
|
|
||||||
|
def get_changeform_initial_data(self, request):
|
||||||
|
import datetime
|
||||||
|
td = datetime.date.today()
|
||||||
|
end = td + datetime.timedelta(30)
|
||||||
|
return {'begin':datetime.date.today, 'end':end}
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||||
|
if db_field.name == 'refer':
|
||||||
|
app_info = generic.get_app_model_info_from_request(request)
|
||||||
|
if app_info and app_info['obj']:
|
||||||
|
kwargs['queryset'] = WorkOrder.objects.exclude(id=app_info['id'])
|
||||||
|
|
||||||
|
return super(WorkOrderAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class LoanAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'JK'
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
list_display = ['code','title','project','loan_amount','applier','status']
|
||||||
|
list_display_links = ['code','title']
|
||||||
|
readonly_fields = ['status','logout_time','logout_amount']
|
||||||
|
raw_id_fields = ['project','user']
|
||||||
|
fields = (
|
||||||
|
('code',),('title','loan_amount',),('description',),('project'),('user','status'),('logout_time','logout_amount',),
|
||||||
|
)
|
||||||
|
extra_buttons = [{'href':'pay','title':_('pay')}]
|
||||||
|
search_fields = ['code','title','user__username']
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
if object_id:
|
||||||
|
try:
|
||||||
|
obj = Loan.objects.get(id=object_id)
|
||||||
|
if obj and obj.status == 'P':
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
return super(LoanAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if obj and obj.user is None:
|
||||||
|
obj.user = request.user
|
||||||
|
super(LoanAdmin,self).save_model(request,obj,form,change)
|
||||||
|
|
||||||
|
|
||||||
|
class ReimbursementItemInline(admin.TabularInline):
|
||||||
|
model = ReimbursementItem
|
||||||
|
raw_id_fields = ['expense_account']
|
||||||
|
|
||||||
|
def get_extra(self, request, obj=None, **kwargs):
|
||||||
|
if obj:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class ReimbursementAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'BX'
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
list_display = ['code','title','project','amount','applier','status']
|
||||||
|
list_display_links = ['code','title']
|
||||||
|
inlines = [ReimbursementItemInline]
|
||||||
|
raw_id_fields = ['project','wo','user','org']
|
||||||
|
readonly_fields = ['loan_amount','pay_time','status','amount']
|
||||||
|
fieldsets = [
|
||||||
|
(None,{'fields':[('code','user'),('title','amount','status',),('description',),('project','wo',)]}),
|
||||||
|
(_('fico'),{'fields':[('org',),('loan',),('logout_amount','pay_amount',)],'classes': ['collapse']})
|
||||||
|
]
|
||||||
|
extra_buttons = [{'href':'pay','title':_('pay')}]
|
||||||
|
search_fields = ['code','title','project__code','project__name','user__username']
|
||||||
|
date_hierarchy = 'begin'
|
||||||
|
|
||||||
|
def get_changeform_initial_data(self, request):
|
||||||
|
apps = generic.get_app_model_info_from_request(request)
|
||||||
|
obj = getattr(apps,'obj',None)
|
||||||
|
current = request.user
|
||||||
|
if obj:
|
||||||
|
current = obj.user
|
||||||
|
sm = Loan.objects.filter(user=current).aggregate(Sum('loan_amount')).get('loan_amount__sum') or 0.00
|
||||||
|
return {'loan_amount':sm}
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if obj and obj.user is None:
|
||||||
|
obj.user = request.user
|
||||||
|
|
||||||
|
super(ReimbursementAdmin,self).save_model(request,obj,form,change)
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
||||||
|
if db_field.name == 'loan':
|
||||||
|
apps = generic.get_app_model_info_from_request(request)
|
||||||
|
current = request.user
|
||||||
|
if apps:
|
||||||
|
obj = apps.get('obj')
|
||||||
|
current = obj.user
|
||||||
|
if obj.status == 'P':
|
||||||
|
kwargs['queryset']=Loan.objects.filter(id=obj.loan.id)
|
||||||
|
else:
|
||||||
|
kwargs['queryset']=Loan.objects.filter(user=current,is_clear=0)
|
||||||
|
else:
|
||||||
|
kwargs['queryset']=Loan.objects.filter(user=current,is_clear=0)
|
||||||
|
return super(ReimbursementAdmin,self).formfield_for_foreignkey(db_field,request,**kwargs)
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
if object_id:
|
||||||
|
try:
|
||||||
|
obj = Reimbursement.objects.get(id=object_id)
|
||||||
|
if obj and obj.status == 'P':
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
extra_context.update(dict(readonly=True))
|
||||||
|
except Exception,e:
|
||||||
|
pass
|
||||||
|
return super(ReimbursementAdmin,self).changeform_view(request,object_id,form_url,extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
class EnrollInline(admin.TabularInline):
|
||||||
|
model = Enroll
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackInline(admin.TabularInline):
|
||||||
|
model = Feedback
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityAdmin(generic.BOAdmin):
|
||||||
|
CODE_PREFIX = 'AC'
|
||||||
|
CODE_NUMBER_WIDTH = 5
|
||||||
|
list_display = ['code','begin_time','end_time','title','classification','room']
|
||||||
|
list_display_links = ['code','title']
|
||||||
|
raw_id_fields = ['room','parent']
|
||||||
|
fieldsets = [
|
||||||
|
(None,{'fields':[('begin_time','end_time',),('title','classification',),('description',),
|
||||||
|
('host','speaker',),('room','location',),('attach',)]}),
|
||||||
|
(_('other info'),{'fields':[('mail_list',),('parent',),('mail_notice','short_message_notice','weixin_notice',)],'classes': ['collapse']})
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_changeform_initial_data(self, request):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
begin = now + datetime.timedelta(hours=12)
|
||||||
|
end = begin + datetime.timedelta(hours=6)
|
||||||
|
return {'begin_time':begin,'end_time':end}
|
||||||
|
|
||||||
|
admin.site.register(WorkOrder,WorkOrderAdmin)
|
||||||
|
admin.site.register(Loan,LoanAdmin)
|
||||||
|
admin.site.register(Reimbursement,ReimbursementAdmin)
|
||||||
|
admin.site.register(Activity,ActivityAdmin)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# created at 15-5-23
|
||||||
|
# coding=utf-8
|
||||||
|
__author__ = 'zhugl'
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class MyAppConfig(AppConfig):
|
||||||
|
name = 'selfhelp'
|
||||||
|
verbose_name = _("self help")
|
||||||
@@ -0,0 +1,330 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import xlrd
|
||||||
|
import decimal
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.text import force_text
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import generic
|
||||||
|
from common import const
|
||||||
|
from mis import settings
|
||||||
|
from basedata.models import Material,ExtraParam,Project,ExpenseAccount,Measure
|
||||||
|
from organ.models import OrgUnit
|
||||||
|
|
||||||
|
|
||||||
|
class WorkOrder(generic.BO):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
index_weight = 1
|
||||||
|
code = models.CharField(_("workorder code"),blank=True,null=True,max_length=const.DB_CHAR_CODE_10)
|
||||||
|
refer = models.ForeignKey("self",verbose_name=_("refer wo"),blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
description = models.TextField(_("description"),blank=True,null=True)
|
||||||
|
business_domain = models.CharField(_("business domain"),max_length=const.DB_CHAR_CODE_4,choices=const.get_value_list('S045'),default='OT')
|
||||||
|
classification = models.CharField(_("classification"),max_length=const.DB_CHAR_CODE_4,choices=const.get_value_list('S044'),default='D')
|
||||||
|
service = models.ForeignKey(Material,verbose_name=_("service name"),null=True,blank=True,limit_choices_to={"is_virtual":"1"})
|
||||||
|
project = models.ForeignKey(Project,verbose_name=_("project"),null=True,blank=True)
|
||||||
|
status = models.CharField(_("status"),blank=True,null=True,default='NEW',max_length=const.DB_CHAR_CODE_6,choices=const.get_value_list('S046'))
|
||||||
|
answer = models.TextField(_("answer"),blank=True,null=True)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||||
|
attach = models.FileField(_('attach'),blank=True,null=True,help_text=u'工单附件,不导入明细。')
|
||||||
|
detail = models.FileField(_('to be imported detail'),blank=True,null=True,help_text=u'您可导入需求明细,模板请参考文档FD0007')
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"%s-%s" % (self.code,self.title)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
super(WorkOrder,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
if self.service:
|
||||||
|
material = self.service
|
||||||
|
if self.woextravalue_set.count() < 1 and material.extraparam_set and material.extraparam_set.count() > 0:
|
||||||
|
for param in material.extraparam_set.all():
|
||||||
|
extra_param = WOExtraValue.objects.create(workorder=self,param_name=param)
|
||||||
|
self.woextravalue_set.add(extra_param)
|
||||||
|
item_count = WOItem.objects.filter(workorder=self).count()
|
||||||
|
if self.detail and item_count == 0:
|
||||||
|
path = os.path.join(settings.MEDIA_ROOT,self.detail.name)
|
||||||
|
workbook = xlrd.open_workbook(path)
|
||||||
|
sheet = workbook.sheet_by_index(0)
|
||||||
|
row_count = sheet.nrows
|
||||||
|
with transaction.atomic():
|
||||||
|
for row_index in range(row_count):
|
||||||
|
row = sheet.row_values(row_index)
|
||||||
|
if row_index == 0:
|
||||||
|
doc_type = row[1]
|
||||||
|
if doc_type.startswith('0'):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
elif row_index < 3:
|
||||||
|
continue
|
||||||
|
material = None
|
||||||
|
measure = None
|
||||||
|
try:
|
||||||
|
measure = Measure.objects.get(code=row[4])
|
||||||
|
except Exception,e:
|
||||||
|
measure = Measure.objects.create(code=row[4],name=force_text(row[5]))
|
||||||
|
try:
|
||||||
|
material = Material.objects.get(code=row[0])
|
||||||
|
except Exception,e:
|
||||||
|
material = Material(code=row[0],name=force_text(row[1]),spec=force_text(row[2]))
|
||||||
|
material.save()
|
||||||
|
WOItem.objects.create(workorder=self,material=material,measure=measure,amount=row[6])
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("workorder apply")
|
||||||
|
verbose_name_plural = _("workorder apply")
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
js = ('js/workorder.js',)
|
||||||
|
|
||||||
|
|
||||||
|
class WOExtraValue(models.Model):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
workorder = models.ForeignKey(WorkOrder,verbose_name=_("workorder"))
|
||||||
|
param_name = models.ForeignKey(ExtraParam,verbose_name=_("extra param"))
|
||||||
|
param_value = models.CharField(_("param value"),blank=True,null=True,max_length=const.DB_CHAR_NAME_40)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("extra value")
|
||||||
|
verbose_name_plural = _("extra values")
|
||||||
|
|
||||||
|
|
||||||
|
class WOItem(models.Model):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
workorder = models.ForeignKey(WorkOrder,verbose_name=_("workorder"))
|
||||||
|
material = models.ForeignKey(Material,verbose_name=_("material"),null=True,blank=True,limit_choices_to={"is_virtual":"0"})
|
||||||
|
amount = models.DecimalField(_("amount"),max_digits=10,decimal_places=4,blank=True,null=True)
|
||||||
|
measure = models.ForeignKey(Measure,verbose_name=_('measure'),blank=True,null=True)
|
||||||
|
price = models.DecimalField(_("price"),max_digits=10,decimal_places=4,blank=True,null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("workorder item")
|
||||||
|
verbose_name_plural = _("workorder items")
|
||||||
|
|
||||||
|
|
||||||
|
class Loan(generic.BO):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
LOAD_STATUS = (
|
||||||
|
('N',_("NEW")),
|
||||||
|
('I',_("IN PROGRESS")),
|
||||||
|
('A',_("APPROVED")),
|
||||||
|
('P',_("PAYED"))
|
||||||
|
)
|
||||||
|
index_weight = 3
|
||||||
|
code = models.CharField(_("loan code"),max_length=const.DB_CHAR_CODE_10,blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
description = models.TextField(_("description"),blank=True,null=True)
|
||||||
|
project = models.ForeignKey(Project,verbose_name=_("project"))
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||||
|
status = models.CharField(_("status"),blank=True,null=True,default='N',max_length=const.DB_CHAR_CODE_2,choices=LOAD_STATUS)
|
||||||
|
logout_time = models.DateTimeField(_("logout time"),blank=True,null=True)
|
||||||
|
loan_amount = models.DecimalField(_("loan amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||||
|
logout_amount = models.DecimalField(_("logout amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||||
|
pay_user = models.CharField(_('pay user'),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
pay_time = models.DateTimeField(_('pay time'),blank=True,null=True)
|
||||||
|
is_clear = models.BooleanField(_('is clear'),default=False)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
import decimal
|
||||||
|
left = self.loan_amount
|
||||||
|
left -= self.logout_amount or decimal.Decimal(0.00)
|
||||||
|
name = '%s%s'%(self.user.last_name,self.user.first_name)
|
||||||
|
return '%s %s %s J:%.2f Y:%.2f' % (self.code,name,self.title,self.loan_amount,left)
|
||||||
|
|
||||||
|
def applier(self):
|
||||||
|
return u'%s%s'%(self.user.last_name,self.user.first_name)
|
||||||
|
|
||||||
|
applier.short_description = _('applier')
|
||||||
|
|
||||||
|
def action_pay(self,request):
|
||||||
|
self.pay_time = datetime.datetime.now()
|
||||||
|
self.pay_user = request.user.username
|
||||||
|
self.status = 'P'
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("loan")
|
||||||
|
verbose_name_plural = _("loans")
|
||||||
|
permissions = (
|
||||||
|
('financial_pay',_("financial pay")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Reimbursement(generic.BO):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
REIM_STATUS = (
|
||||||
|
('N',_("NEW")),
|
||||||
|
('I',_("IN PROGRESS")),
|
||||||
|
('A',_("APPROVED")),
|
||||||
|
('P',_("PAYED"))
|
||||||
|
)
|
||||||
|
index_weight = 2
|
||||||
|
code = models.CharField(_("fee code"),max_length=const.DB_CHAR_CODE_10,blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
description = models.TextField(_("description"),blank=True,null=True)
|
||||||
|
project = models.ForeignKey(Project,verbose_name=_("project"))
|
||||||
|
wo = models.ForeignKey(WorkOrder,verbose_name=_("work order"),null=True,blank=True)
|
||||||
|
user = models.ForeignKey(User,verbose_name=_("user"),blank=True,null=True)
|
||||||
|
org = models.ForeignKey(OrgUnit,verbose_name=_("cost center"),blank=True,null=True)
|
||||||
|
bank_account = models.CharField(_("bank account"),max_length=const.DB_CHAR_NAME_120,blank=True,null=True)
|
||||||
|
status = models.CharField(_("status"),blank=True,null=True,default='N',max_length=const.DB_CHAR_CODE_2,choices=REIM_STATUS)
|
||||||
|
amount = models.DecimalField(_("amount of money"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||||
|
loan = models.ForeignKey(Loan,verbose_name=_("loan record"),blank=True,null=True)
|
||||||
|
loan_amount = models.DecimalField(_("loan amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||||
|
logout_amount = models.DecimalField(_("logout amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||||
|
pay_amount = models.DecimalField(_("pay amount"),max_digits=10,decimal_places=2,blank=True,null=True)
|
||||||
|
pay_time = models.DateTimeField(_("pay time"),blank=True,null=True)
|
||||||
|
pay_user = models.CharField(_('pay user'),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
|
||||||
|
def applier(self):
|
||||||
|
return u'%s%s'%(self.user.last_name,self.user.first_name)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
import decimal
|
||||||
|
if self.loan:
|
||||||
|
left = self.loan.loan_amount
|
||||||
|
left -= self.loan.logout_amount or decimal.Decimal(0.00)
|
||||||
|
if self.logout_amount is None or self.logout_amount == '':
|
||||||
|
if self.amount < left:
|
||||||
|
self.logout_amount = self.amount
|
||||||
|
else:
|
||||||
|
self.logout_amount = left
|
||||||
|
self.pay_amount = self.amount
|
||||||
|
if self.logout_amount:
|
||||||
|
self.pay_amount -= self.logout_amount
|
||||||
|
super(Reimbursement,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
|
||||||
|
def action_pay(self,request):
|
||||||
|
if self.loan:
|
||||||
|
if self.logout_amount is None or self.logout_amount == '':
|
||||||
|
self.logout_amount = self.amount
|
||||||
|
if self.logout_amount < 0:
|
||||||
|
raise Exception(u'核销金额小于0')
|
||||||
|
|
||||||
|
if self.loan.logout_amount is None:
|
||||||
|
self.loan.logout_amount = self.logout_amount
|
||||||
|
else:
|
||||||
|
self.loan.logout_amount += self.logout_amount
|
||||||
|
|
||||||
|
if self.loan.loan_amount == self.loan.logout_amount:
|
||||||
|
self.loan.is_clear=True
|
||||||
|
|
||||||
|
self.loan.logout_time = datetime.datetime.now()
|
||||||
|
self.loan.save()
|
||||||
|
|
||||||
|
if self.amount > self.logout_amount:
|
||||||
|
self.pay_amount = self.amount - self.logout_amount
|
||||||
|
|
||||||
|
self.pay_time = datetime.datetime.now()
|
||||||
|
self.pay_user = request.user.username
|
||||||
|
self.status = 'P'
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
applier.short_description = _('applier')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("reimbursement")
|
||||||
|
verbose_name_plural = _("reimbursements")
|
||||||
|
permissions = (
|
||||||
|
('financial_pay',_("financial pay")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReimbursementItem(models.Model):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
reimbursement = models.ForeignKey(Reimbursement,verbose_name=_("reimbursement"))
|
||||||
|
expense_account = models.ForeignKey(ExpenseAccount,verbose_name=_("expenses account"))
|
||||||
|
begin = models.DateField(_("occur date"),default=datetime.date.today)
|
||||||
|
amount = models.DecimalField(_("amount of money"),max_digits=10,decimal_places=2)
|
||||||
|
memo = models.CharField(_("memo"),max_length=const.DB_CHAR_NAME_40,blank=True,null=True)
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
super(ReimbursementItem,self).save(force_insert,force_update,using,update_fields)
|
||||||
|
sql = 'UPDATE selfhelp_reimbursement SET amount = (SELECT SUM(amount) FROM selfhelp_reimbursementitem WHERE reimbursement_id = %s) WHERE id = %s'
|
||||||
|
params = [self.reimbursement.id,self.reimbursement.id]
|
||||||
|
generic.update(sql,params)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("fee item")
|
||||||
|
verbose_name_plural = _("fee items")
|
||||||
|
|
||||||
|
|
||||||
|
class Activity(generic.BO):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
CLASSIFICATION = (
|
||||||
|
('T',_("Train")),
|
||||||
|
('M',_("Meeting")),
|
||||||
|
('G',_("Community")),
|
||||||
|
)
|
||||||
|
index_index_weight = 4
|
||||||
|
begin_time = models.DateTimeField(_('begin time'))
|
||||||
|
end_time = models.DateTimeField(_('end time'))
|
||||||
|
enroll_deadline = models.DateTimeField(_('enroll deadline'),blank=True,null=True)
|
||||||
|
code = models.CharField(_("code"),max_length=const.DB_CHAR_NAME_20,blank=True,null=True)
|
||||||
|
title = models.CharField(_("title"),max_length=const.DB_CHAR_NAME_120)
|
||||||
|
parent = models.ForeignKey('self',verbose_name=_("parent"),blank=True,null=True)
|
||||||
|
description = models.TextField(_("description"),blank=True,null=True)
|
||||||
|
host = models.CharField(_("host"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||||
|
speaker = models.CharField(_("speaker"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||||
|
accept_enroll = models.BooleanField(_("accept enroll"),default=1)
|
||||||
|
room = models.ForeignKey(Material,verbose_name=_("room"),blank=True,null=True,limit_choices_to={'tp':20})
|
||||||
|
location = models.CharField(_("location"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||||
|
classification = models.CharField(_("classification"),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=CLASSIFICATION,default='M')
|
||||||
|
mail_list = models.TextField(_("mail list"),blank=True,null=True)
|
||||||
|
mail_notice = models.BooleanField(_("mail notice"),default=1)
|
||||||
|
short_message_notice = models.BooleanField(_("short message notice"),default=1)
|
||||||
|
weixin_notice = models.BooleanField(_("weixin notice"),default=1)
|
||||||
|
status = models.BooleanField(_("published"),default=0)
|
||||||
|
publish_time = models.DateTimeField(_("publish time"),blank=True,null=True)
|
||||||
|
attach = models.FileField(_("attach"),blank=True,null=True,upload_to='activity')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("activity")
|
||||||
|
verbose_name_plural = _("activities")
|
||||||
|
|
||||||
|
|
||||||
|
class Feedback(models.Model):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
RANK = (
|
||||||
|
('A','A'),
|
||||||
|
('B','B'),
|
||||||
|
('C','C'),
|
||||||
|
('D','D'),
|
||||||
|
)
|
||||||
|
activity = models.ForeignKey(Activity)
|
||||||
|
user = models.ForeignKey(User)
|
||||||
|
feed_time = models.DateTimeField(_("feedback time"),auto_now_add=True)
|
||||||
|
rank = models.CharField(_('rank'),max_length=const.DB_CHAR_CODE_2,blank=True,null=True,choices=RANK,default='B')
|
||||||
|
comment = models.CharField(_("suggest"),blank=True,null=True,max_length=const.DB_CHAR_NAME_80)
|
||||||
|
|
||||||
|
|
||||||
|
class Enroll(models.Model):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
activity = models.ForeignKey(Activity)
|
||||||
|
user = models.ForeignKey(User)
|
||||||
|
enroll_time = models.DateTimeField(_("enroll time"),auto_now_add=True)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from django.conf.urls import include, url,static
|
||||||
|
import selfhelp.views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r"(?P<model>\w+)/(?P<object_id>\d+)/pay", selfhelp.views.pay_action),
|
||||||
|
]
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.contrib.admin import site
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db import connection
|
||||||
|
from django.http.response import HttpResponseRedirect
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
|
||||||
|
def pay_action(request,model,object_id):
|
||||||
|
title = _("Are you sure?")
|
||||||
|
ct = ContentType.objects.get(app_label='selfhelp',model=model)
|
||||||
|
obj = ct.get_object_for_this_type(id=int(object_id))
|
||||||
|
opts = obj._meta
|
||||||
|
objects_name = force_text(opts.verbose_name)
|
||||||
|
|
||||||
|
if model == 'reimbursement':
|
||||||
|
loan = obj.loan
|
||||||
|
amount = obj.logout_amount
|
||||||
|
if loan and (amount is None or amount< 0):
|
||||||
|
messages.error(request,u'您选择了借款单据,但是未正确填写核销金额,请在\'财务信息\'栏目中更正')
|
||||||
|
return HttpResponseRedirect("/admin/selfhelp/%s/%s"%(model,object_id))
|
||||||
|
|
||||||
|
if request.POST.get("post"):
|
||||||
|
try:
|
||||||
|
obj.action_pay(request)
|
||||||
|
messages.success(request,_('action successfully'))
|
||||||
|
except Exception,e:
|
||||||
|
messages.error(request,e)
|
||||||
|
|
||||||
|
return HttpResponseRedirect("/admin/selfhelp/%s/%s"%(model,object_id))
|
||||||
|
|
||||||
|
context = dict(
|
||||||
|
site.each_context(request),
|
||||||
|
title=title,
|
||||||
|
opts=opts,
|
||||||
|
objects_name=objects_name,
|
||||||
|
object=obj,
|
||||||
|
action_name=_('pay')
|
||||||
|
)
|
||||||
|
request.current_app = site.name
|
||||||
|
|
||||||
|
return TemplateResponse(request,'admin/invent/stockin/in_confirmation.html', context)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
table#workflow-history{
|
||||||
|
width:100%
|
||||||
|
}
|
||||||
|
table#workflow-history tbody th,table#workflow-history tbody td.col {
|
||||||
|
width: 16em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submitlink {
|
||||||
|
padding-left: 12px;
|
||||||
|
background: url(../img/icon-yes.gif) 0 .25em no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.submitlink:link, a.submitlink:visited {
|
||||||
|
color: #CC3434;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.submitlink:hover {
|
||||||
|
color: #993333;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.control{
|
||||||
|
width:4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.next-node{
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
ul.node-users{
|
||||||
|
list-style:none;
|
||||||
|
}
|
||||||
|
p.tooltip{
|
||||||
|
color:#999;
|
||||||
|
}
|
||||||
|
li.done-link{
|
||||||
|
padding-left: 12px;
|
||||||
|
background: url(../img/icon_yes.gif) 0 .2em no-repeat;
|
||||||
|
}
|
||||||
@@ -0,0 +1,984 @@
|
|||||||
|
/* 智捷ERP SmartJet ERP - 1:1 High-Fidelity UI System v4.0 */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary: #6366f1;
|
||||||
|
--primary-light: #818cf8;
|
||||||
|
--primary-bg: #f5f3ff;
|
||||||
|
|
||||||
|
--slate-50: #f8fafc;
|
||||||
|
--slate-100: #f1f5f9;
|
||||||
|
--slate-200: #e2e8f0;
|
||||||
|
--slate-300: #cbd5e1;
|
||||||
|
--slate-400: #94a3b8;
|
||||||
|
--slate-500: #64748b;
|
||||||
|
--slate-600: #475569;
|
||||||
|
--slate-700: #334155;
|
||||||
|
--slate-800: #1e293b;
|
||||||
|
--slate-900: #0f172a;
|
||||||
|
|
||||||
|
--success: #10b981;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--danger: #ef4444;
|
||||||
|
--info: #3b82f6;
|
||||||
|
|
||||||
|
--sidebar-width: 240px;
|
||||||
|
--sidebar-collapsed-width: 0px;
|
||||||
|
--topbar-height: 64px;
|
||||||
|
--radius-lg: 16px;
|
||||||
|
--radius-md: 10px;
|
||||||
|
--shadow-card: 0 10px 15px -3px rgba(0, 0, 0, 0.04), 0 4px 6px -2px rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #f1f5f9 !important;
|
||||||
|
color: var(--slate-800) !important;
|
||||||
|
font-family: 'Plus Jakarta Sans', -apple-system, sans-serif !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
display: flex !important;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-width: 0 !important;
|
||||||
|
max-width: 100vw;
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- SIDEBAR --- */
|
||||||
|
#header {
|
||||||
|
width: var(--sidebar-width) !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
position: fixed !important;
|
||||||
|
left: 0; top: 0;
|
||||||
|
background: #ffffff !important;
|
||||||
|
border-right: 1px solid var(--slate-200) !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
z-index: 1000 !important;
|
||||||
|
color: var(--slate-800) !important;
|
||||||
|
line-height: normal !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 覆盖 Django admin 默认深色顶栏链接样式(白字导致侧栏文字不可见) */
|
||||||
|
#header a:link,
|
||||||
|
#header a:visited {
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header a:focus,
|
||||||
|
#header a:hover {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-section {
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: linear-gradient(135deg, #6366f1, #a855f7);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-text h1 {
|
||||||
|
font-size: 18px !important;
|
||||||
|
font-weight: 800 !important;
|
||||||
|
color: #1e293b !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-text span {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--slate-400);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-nav {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 16px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-group-label {
|
||||||
|
padding: 20px 12px 10px 12px;
|
||||||
|
color: var(--slate-400);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--slate-600) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover,
|
||||||
|
.nav-item:focus {
|
||||||
|
background: var(--slate-50);
|
||||||
|
color: var(--primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active,
|
||||||
|
.nav-item.active:link,
|
||||||
|
.nav-item.active:visited {
|
||||||
|
background: var(--primary-bg) !important;
|
||||||
|
color: var(--primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item i { margin-right: 12px; font-size: 18px; width: 20px; text-align: center; }
|
||||||
|
.nav-item .arrow { margin-left: auto; font-size: 10px; color: var(--slate-300); }
|
||||||
|
|
||||||
|
.sidebar-footer {
|
||||||
|
padding: 16px;
|
||||||
|
border-top: 1px solid var(--slate-100);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1002;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-btn {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.nav-item-btn:hover,
|
||||||
|
button.nav-item-btn:focus {
|
||||||
|
background: var(--slate-50);
|
||||||
|
color: var(--primary) !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 侧栏收起 */
|
||||||
|
#header {
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.sidebar-collapsed #header {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.sidebar-collapsed #main {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
max-width: 100vw !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- TOPBAR --- */
|
||||||
|
#main {
|
||||||
|
flex: 1 1 auto !important;
|
||||||
|
margin-left: var(--sidebar-width) !important;
|
||||||
|
width: auto !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
max-width: calc(100vw - var(--sidebar-width)) !important;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
height: var(--topbar-height);
|
||||||
|
background: #ffffff !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 32px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 900;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-left { display: flex; align-items: center; flex: 1; }
|
||||||
|
.topbar-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
background: #ffffff !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
position: relative;
|
||||||
|
z-index: 901;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-new {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--slate-400);
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-new span { color: var(--slate-800); font-weight: 600; }
|
||||||
|
|
||||||
|
.search-btn, .icon-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--slate-500);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn:hover, .icon-btn:hover,
|
||||||
|
.search-btn:focus, .icon-btn:focus {
|
||||||
|
background: var(--slate-100);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-menu-wrap {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 20px;
|
||||||
|
border-left: 1px solid var(--slate-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-profile {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #ffffff !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-profile:hover,
|
||||||
|
.user-profile:focus {
|
||||||
|
background: var(--slate-50) !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-caret {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--slate-400);
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-dropdown {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
right: 0;
|
||||||
|
min-width: 140px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid var(--slate-200);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 6px;
|
||||||
|
z-index: 1100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-dropdown.open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-dropdown a {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--slate-700) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-dropdown a:hover {
|
||||||
|
background: var(--slate-50);
|
||||||
|
color: var(--primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--slate-200);
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info { display: flex; flex-direction: column; background: transparent; }
|
||||||
|
.user-name { font-weight: 700; font-size: 13px; color: var(--slate-800) !important; opacity: 1 !important; }
|
||||||
|
.user-role { font-size: 11px; color: var(--slate-500) !important; opacity: 1 !important; }
|
||||||
|
|
||||||
|
/* --- CONTENT --- */
|
||||||
|
#content {
|
||||||
|
padding: 32px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard #content {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content-main {
|
||||||
|
float: none !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard #content > h1 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content > h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--slate-900);
|
||||||
|
margin: 0 0 24px;
|
||||||
|
padding-right: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colM, .colMS {
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#changelist, #changelist-form, .results {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- STATS CARDS --- */
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-header { display: flex; justify-content: space-between; margin-bottom: 12px; }
|
||||||
|
.stat-icon-bg { width: 40px; height: 40px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; font-size: 18px; }
|
||||||
|
.stat-info-icon { color: var(--slate-300); font-size: 14px; cursor: help; }
|
||||||
|
|
||||||
|
.stat-value { font-size: 24px; font-weight: 800; color: var(--slate-900); margin-bottom: 8px; }
|
||||||
|
.stat-label { font-size: 14px; color: var(--slate-500); font-weight: 500; }
|
||||||
|
|
||||||
|
.stat-footer { display: flex; align-items: center; justify-content: space-between; margin-top: 16px; border-top: 1px solid var(--slate-50); padding-top: 16px; }
|
||||||
|
.stat-trend { font-size: 12px; font-weight: 600; display: flex; align-items: center; gap: 4px; }
|
||||||
|
.trend-up { color: var(--success); }
|
||||||
|
.trend-down { color: var(--danger); }
|
||||||
|
|
||||||
|
.sparkline { width: 60px; height: 30px; }
|
||||||
|
|
||||||
|
/* --- DATA MODULES --- */
|
||||||
|
.dashboard-main-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
|
||||||
|
gap: 24px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
padding: 24px 32px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--slate-900);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- TABLE STYLES --- */
|
||||||
|
.table-container { overflow-x: auto; max-width: 100%; padding: 0 12px 12px 12px; }
|
||||||
|
table { width: 100%; max-width: 100%; border-collapse: collapse; }
|
||||||
|
th { text-align: left; padding: 12px 20px; font-size: 12px; color: var(--slate-400); font-weight: 600; border-bottom: 1px solid var(--slate-100); }
|
||||||
|
td { padding: 16px 20px; font-size: 13px; color: var(--slate-700); border-bottom: 1px solid var(--slate-50); }
|
||||||
|
tr:last-child td { border: none; }
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.badge-new { background: #eef2ff; color: #6366f1; }
|
||||||
|
.badge-audit { background: #ecfdf5; color: #10b981; }
|
||||||
|
.badge-update { background: #eff6ff; color: #3b82f6; }
|
||||||
|
.badge-submit { background: #fff7ed; color: #f97316; }
|
||||||
|
|
||||||
|
/* --- TASK LIST --- */
|
||||||
|
.task-list { padding: 0 24px 24px 24px; }
|
||||||
|
.task-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid var(--slate-50);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.task-item:hover { border-color: var(--slate-200); background: var(--slate-50); }
|
||||||
|
|
||||||
|
.task-icon { width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 16px; color: white; }
|
||||||
|
.task-content { flex: 1; }
|
||||||
|
.task-name { font-size: 13px; font-weight: 700; color: var(--slate-800); }
|
||||||
|
.task-info { font-size: 11px; color: var(--slate-400); margin-top: 2px; }
|
||||||
|
.task-time { font-size: 11px; color: var(--slate-400); }
|
||||||
|
|
||||||
|
.priority-tag { font-size: 10px; font-weight: 800; padding: 2px 6px; border-radius: 4px; text-transform: uppercase; margin-top: 8px; display: inline-block; }
|
||||||
|
.p-urgent { background: #fef2f2; color: #ef4444; }
|
||||||
|
.p-high { background: #fff7ed; color: #f97316; }
|
||||||
|
.p-medium { background: #fffbeb; color: #f59e0b; }
|
||||||
|
.p-low { background: #f0fdf4; color: #10b981; }
|
||||||
|
|
||||||
|
.view-all { text-align: center; padding: 20px; border-top: 1px solid var(--slate-50); }
|
||||||
|
.view-all a { color: var(--primary); text-decoration: none; font-size: 13px; font-weight: 700; }
|
||||||
|
|
||||||
|
/* --- ADMIN FORM ACTIONS --- */
|
||||||
|
#content-main {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object-tools {
|
||||||
|
float: none !important;
|
||||||
|
position: absolute !important;
|
||||||
|
top: 32px;
|
||||||
|
right: 32px;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
z-index: 5;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object-tools li {
|
||||||
|
height: auto !important;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object-tools a:link,
|
||||||
|
.object-tools a:visited,
|
||||||
|
.object-tools a.historylink {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center;
|
||||||
|
float: none !important;
|
||||||
|
padding: 8px 16px !important;
|
||||||
|
background: var(--slate-600) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
text-transform: none !important;
|
||||||
|
letter-spacing: 0 !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.object-tools a:hover,
|
||||||
|
.object-tools a:focus {
|
||||||
|
background: var(--slate-700) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-row {
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
overflow: visible !important;
|
||||||
|
padding: 16px 20px !important;
|
||||||
|
margin: 24px 0 0 !important;
|
||||||
|
background: #ffffff !important;
|
||||||
|
border: 1px solid var(--slate-200) !important;
|
||||||
|
border-radius: var(--radius-md) !important;
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-row p {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-row p.deletelink-box {
|
||||||
|
float: none !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-row a.deletelink {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: auto !important;
|
||||||
|
min-height: 38px;
|
||||||
|
line-height: 1.4 !important;
|
||||||
|
padding: 10px 20px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
background: var(--danger) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-row a.deletelink:hover,
|
||||||
|
.submit-row a.deletelink:focus {
|
||||||
|
background: #dc2626 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-row input,
|
||||||
|
.submit-row a.button,
|
||||||
|
.submit-row .button {
|
||||||
|
height: auto !important;
|
||||||
|
min-height: 38px;
|
||||||
|
line-height: 1.4 !important;
|
||||||
|
padding: 10px 20px !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
text-transform: none !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-row input.default {
|
||||||
|
background: var(--primary) !important;
|
||||||
|
border-color: var(--primary) !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-row input[name="_addanother"],
|
||||||
|
.submit-row input[name="_continue"],
|
||||||
|
.submit-row input[name="_saveasnew"] {
|
||||||
|
background: #ffffff !important;
|
||||||
|
color: var(--slate-700) !important;
|
||||||
|
border: 1px solid var(--slate-300) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 双栏选择器 */
|
||||||
|
.selector {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector .selector-available h2,
|
||||||
|
.selector .selector-chosen h2 {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--slate-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector select {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector .selector-filter label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--slate-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单模块 */
|
||||||
|
.module h2, fieldset.module h2 {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--slate-800);
|
||||||
|
background: var(--slate-50) !important;
|
||||||
|
border-bottom: 1px solid var(--slate-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row label {
|
||||||
|
color: var(--slate-700) !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide messy native elements */
|
||||||
|
#footer, .breadcrumbs, #content-related { display: none !important; }
|
||||||
|
|
||||||
|
@media (max-width: 1280px) {
|
||||||
|
.stats-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-main-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.stats-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
padding: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- LOGIN PAGE --- */
|
||||||
|
body.login-page {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: var(--slate-100);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-shell {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 48px;
|
||||||
|
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #a855f7 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at 20% 80%, rgba(255,255,255,0.12) 0%, transparent 50%),
|
||||||
|
radial-gradient(circle at 80% 20%, rgba(255,255,255,0.08) 0%, transparent 40%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-inner {
|
||||||
|
position: relative;
|
||||||
|
max-width: 420px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-logo {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand h1 {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-tagline {
|
||||||
|
font-size: 13px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-desc {
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 0 0 32px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-features {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-features li {
|
||||||
|
padding: 10px 0;
|
||||||
|
padding-left: 24px;
|
||||||
|
position: relative;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-features li::before {
|
||||||
|
content: '✓';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-panel {
|
||||||
|
flex: 0 0 480px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 32px;
|
||||||
|
background: var(--slate-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
padding: 40px 36px;
|
||||||
|
border: 1px solid var(--slate-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card-header {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card-header h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--slate-900);
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card-header p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--slate-500);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-error {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #dc2626;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--slate-700);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field input[type="text"],
|
||||||
|
.login-field input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
border: 1px solid var(--slate-300);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: #ffffff;
|
||||||
|
color: var(--slate-800);
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field input[type="text"]:focus,
|
||||||
|
.login-field input[type="password"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 3px var(--primary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-submit {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: inherit;
|
||||||
|
color: #ffffff;
|
||||||
|
background: linear-gradient(135deg, var(--primary), #7c3aed);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s, transform 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-submit:hover {
|
||||||
|
opacity: 0.92;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-submit:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-demo {
|
||||||
|
background: var(--primary-bg);
|
||||||
|
border: 1px solid #e0e7ff;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-demo-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-demo-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--slate-600);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-demo-row span {
|
||||||
|
min-width: 48px;
|
||||||
|
color: var(--slate-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-demo-row code {
|
||||||
|
font-family: 'Consolas', monospace;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--slate-800);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-demo-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: #e0e7ff;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer-link {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer-link a {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--slate-400);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer-link a:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.login-shell {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand {
|
||||||
|
flex: none;
|
||||||
|
padding: 32px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-inner {
|
||||||
|
max-width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-logo {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-features {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-panel {
|
||||||
|
flex: 1;
|
||||||
|
padding: 24px 16px 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 122 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 299 B |
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
author:zhugl
|
||||||
|
date:2015-05-15
|
||||||
|
*/
|
||||||
|
(function($) {
|
||||||
|
$(document).ready(function() {
|
||||||
|
/**
|
||||||
|
set operation type
|
||||||
|
*/
|
||||||
|
$("fieldset.workflow input[type='radio']").on('click',function(){
|
||||||
|
operation = $(this).val();
|
||||||
|
next = "approve/"+operation;
|
||||||
|
$("#workflow_approve").attr("href",next);
|
||||||
|
});
|
||||||
|
try{
|
||||||
|
$("div.inline-group table tbody tr.form-row").removeClass('has_original');
|
||||||
|
$("div.inline-group table tbody tr.form-row td:first-child").find('p').hide();
|
||||||
|
}catch(e){
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})(django.jQuery);
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function ready(fn) {
|
||||||
|
if (document.readyState !== 'loading') {
|
||||||
|
fn();
|
||||||
|
} else {
|
||||||
|
document.addEventListener('DOMContentLoaded', fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSidebar() {
|
||||||
|
var collapsed = document.body.classList.toggle('sidebar-collapsed');
|
||||||
|
try {
|
||||||
|
localStorage.setItem('erp-sidebar-collapsed', collapsed ? '1' : '0');
|
||||||
|
} catch (e) {}
|
||||||
|
updateCollapseLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCollapseLabel() {
|
||||||
|
var btn = document.getElementById('sidebarCollapseBtn');
|
||||||
|
if (!btn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var collapsed = document.body.classList.contains('sidebar-collapsed');
|
||||||
|
btn.innerHTML = collapsed
|
||||||
|
? '<i>→</i> 展开菜单'
|
||||||
|
: '<i>←</i> 收起菜单';
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreSidebarState() {
|
||||||
|
try {
|
||||||
|
if (localStorage.getItem('erp-sidebar-collapsed') === '1') {
|
||||||
|
document.body.classList.add('sidebar-collapsed');
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
updateCollapseLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleUserMenu() {
|
||||||
|
var menu = document.getElementById('userDropdown');
|
||||||
|
if (!menu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var open = menu.classList.toggle('open');
|
||||||
|
if (open) {
|
||||||
|
document.addEventListener('click', closeUserMenuOutside);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('click', closeUserMenuOutside);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeUserMenuOutside(e) {
|
||||||
|
var menu = document.getElementById('userDropdown');
|
||||||
|
var profile = document.getElementById('userProfileToggle');
|
||||||
|
if (!menu || !profile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!menu.contains(e.target) && !profile.contains(e.target)) {
|
||||||
|
menu.classList.remove('open');
|
||||||
|
document.removeEventListener('click', closeUserMenuOutside);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindClick(id, handler) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) {
|
||||||
|
el.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handler(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ready(function () {
|
||||||
|
restoreSidebarState();
|
||||||
|
|
||||||
|
bindClick('topbarMenuBtn', toggleSidebar);
|
||||||
|
bindClick('sidebarCollapseBtn', toggleSidebar);
|
||||||
|
bindClick('topbarNotifyBtn', function () {
|
||||||
|
window.location.href = '/admin/workflow/todolist/';
|
||||||
|
});
|
||||||
|
bindClick('userProfileToggle', toggleUserMenu);
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Created by Administrator on 15-5-23.
|
||||||
|
*/
|
||||||
|
(function($) {
|
||||||
|
$(document).ready(function() {
|
||||||
|
/**
|
||||||
|
set operation type
|
||||||
|
*/
|
||||||
|
$("#woextravalue_set-group select").attr('readonly','true')
|
||||||
|
});
|
||||||
|
})(django.jQuery);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
default_app_config = 'syscfg.apps.SysConfig'
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.forms import ModelForm,DateField
|
||||||
|
from syscfg.models import *
|
||||||
|
from common import generic
|
||||||
|
|
||||||
|
|
||||||
|
class SiteForm(ModelForm):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Site
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SiteAdmin(admin.ModelAdmin):
|
||||||
|
list_per_page = 10
|
||||||
|
list_display = ['name', 'begin', 'end']
|
||||||
|
fields = (('begin', 'end'), 'name', 'description', 'user')
|
||||||
|
filter_horizontal = ['user']
|
||||||
|
form = SiteForm
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleAdmin(generic.BOAdmin):
|
||||||
|
CODE_NUMBER_WIDTH = 3
|
||||||
|
CODE_PREFIX = 'U'
|
||||||
|
list_display = ['code','name','parent','status']
|
||||||
|
ordering = ['weight']
|
||||||
|
raw_id_fields = ['parent']
|
||||||
|
|
||||||
|
|
||||||
|
class MenuAdmin(generic.BOAdmin):
|
||||||
|
CODE_NUMBER_WIDTH = 3
|
||||||
|
CODE_PREFIX = 'M'
|
||||||
|
|
||||||
|
list_display = ['code','name','module','status']
|
||||||
|
list_filter = ['module']
|
||||||
|
ordering = ['weight']
|
||||||
|
raw_id_fields = ['module']
|
||||||
|
|
||||||
|
|
||||||
|
class RoleAdmin(generic.BOAdmin):
|
||||||
|
CODE_NUMBER_WIDTH = 3
|
||||||
|
CODE_PREFIX = 'R'
|
||||||
|
list_display = ['code','name','status']
|
||||||
|
filter_horizontal = ['users','menus']
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Site, SiteAdmin)
|
||||||
|
admin.site.register(Module,ModuleAdmin)
|
||||||
|
admin.site.register(Menu,MenuAdmin)
|
||||||
|
admin.site.register(Role,RoleAdmin)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
__author__ = 'zhugl'
|
||||||
|
# created at 15-4-22
|
||||||
|
from django.apps.config import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class SysConfig(AppConfig):
|
||||||
|
name = 'syscfg'
|
||||||
|
verbose_name = _('SysConfig')
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from common import const
|
||||||
|
from common import generic
|
||||||
|
|
||||||
|
|
||||||
|
class Site(models.Model):
|
||||||
|
"""
|
||||||
|
站点,一个站点下可有多个公司,处于同一个站点下的用户逻辑上位于同一个组织
|
||||||
|
"""
|
||||||
|
index_weight = 1
|
||||||
|
begin = models.DateField(_('begin date'), blank=True,null=True)
|
||||||
|
end = models.DateField(_('end date'), blank=True,null=True)
|
||||||
|
name = models.CharField(_('site name'), max_length=const.DB_CHAR_NAME_40)
|
||||||
|
description = models.TextField(_('site description'),blank=True,null=True)
|
||||||
|
user = models.ManyToManyField(User,verbose_name=_('administrator'))
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'%s'%self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Site')
|
||||||
|
verbose_name_plural = _('Site')
|
||||||
|
|
||||||
|
|
||||||
|
class Module(generic.BO):
|
||||||
|
"""
|
||||||
|
模块管理
|
||||||
|
"""
|
||||||
|
index_weight = 2
|
||||||
|
code = models.CharField(_("module code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||||
|
name = models.CharField(_("module name"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
url = models.URLField(_("module url"),blank=True,null=True,max_length=const.DB_CHAR_NAME_80)
|
||||||
|
weight = models.IntegerField(_("weight"),blank=True,null=True,default=99)
|
||||||
|
icon = models.CharField(_("style class"),blank=True,null=True,max_length=const.DB_CHAR_NAME_40)
|
||||||
|
parent = models.ForeignKey('self',blank=True,null=True,verbose_name=_("parent"))
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("module")
|
||||||
|
verbose_name_plural = _("module")
|
||||||
|
|
||||||
|
|
||||||
|
class Menu(generic.BO):
|
||||||
|
"""
|
||||||
|
菜单管理
|
||||||
|
"""
|
||||||
|
index_weight = 3
|
||||||
|
module = models.ForeignKey(Module,verbose_name=_("module"))
|
||||||
|
code = models.CharField(_("menu code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||||
|
name = models.CharField(_("menu name"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
url = models.URLField(_("menu url"),blank=True,null=True,max_length=const.DB_CHAR_NAME_80)
|
||||||
|
weight = models.IntegerField(_("weight"),blank=True,null=True,default=99)
|
||||||
|
icon = models.CharField(_("style class"),blank=True,null=True,max_length=const.DB_CHAR_NAME_40)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("menu")
|
||||||
|
verbose_name_plural = _("menu")
|
||||||
|
|
||||||
|
|
||||||
|
class Role(generic.BO):
|
||||||
|
"""
|
||||||
|
角色管理,分配用户所拥有的菜单
|
||||||
|
"""
|
||||||
|
index_weight = 4
|
||||||
|
code = models.CharField(_("role code"),max_length=const.DB_CHAR_CODE_6,blank=True,null=True)
|
||||||
|
name = models.CharField(_("role name"),max_length=const.DB_CHAR_NAME_40)
|
||||||
|
description = models.CharField(_("description"),max_length=const.DB_CHAR_NAME_80,blank=True,null=True)
|
||||||
|
status = models.BooleanField(_("in use"),default=True)
|
||||||
|
parent = models.ForeignKey('self',blank=True,null=True,verbose_name=_("parent"))
|
||||||
|
users = models.ManyToManyField(User,verbose_name=_("role users"),blank=True)
|
||||||
|
menus = models.ManyToManyField(Menu,verbose_name=_("role menus"),blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("role")
|
||||||
|
verbose_name_plural = _("role")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||