feat: update

This commit is contained in:
Czw 2023-10-20 17:22:57 +08:00
parent 66280f72f5
commit 54c2d98859
11 changed files with 316 additions and 136 deletions

View file

@ -13,11 +13,13 @@ class ProductionOrder(Model):
number = CharField(max_length=32, verbose_name='编号')
is_related = BooleanField(default=False, verbose_name='关联状态')
sales_order = ForeignKey('sales.SalesOrder', on_delete=PROTECT, null=True, related_name='production_orders', verbose_name='销售单')
sales_order = ForeignKey(
'sales.SalesOrder', on_delete=PROTECT, null=True, related_name='production_orders', verbose_name='销售单')
goods = ForeignKey('goods.Goods', on_delete=PROTECT, related_name='production_orders', verbose_name='产品')
total_quantity = FloatField(verbose_name='生产总数')
quantity_produced = FloatField(verbose_name='已生产数量')
remain_quantity = FloatField(verbose_name='剩余数量')
stock_in_quantity = FloatField(default=0, verbose_name='入库数量')
start_time = DateTimeField(null=True, verbose_name='开始时间')
end_time = DateTimeField(null=True, verbose_name='结束时间')
status = CharField(max_length=32, choices=Status.choices, default=Status.IN_PLAN, verbose_name='状态')
@ -43,11 +45,13 @@ class ProductionOrder(Model):
class ProductionRecord(Model):
"""生产记录"""
production_order = ForeignKey('production.ProductionOrder', on_delete=CASCADE, related_name='production_records', verbose_name='生产单')
goods = ForeignKey('goods.Goods', on_delete=PROTECT, related_name='production_records', verbose_name='产品')
production_order = ForeignKey(
'production.ProductionOrder', on_delete=CASCADE, related_name='production_records', verbose_name='生产单')
goods = ForeignKey(
'goods.Goods', on_delete=PROTECT, related_name='production_records', verbose_name='产品')
production_quantity = FloatField(verbose_name='生产数量')
creator = ForeignKey('system.User', on_delete=PROTECT,
related_name='created_production_records', verbose_name='创建人')
creator = ForeignKey(
'system.User', on_delete=PROTECT, related_name='created_production_records', verbose_name='创建人')
create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间')
team = ForeignKey('system.Team', on_delete=CASCADE, related_name='production_records')

View file

@ -5,6 +5,11 @@ class NumberResponse(Serializer):
number = CharField(label='编号')
class ProductionStockInRequest(Serializer):
warehouse = IntegerField(label='仓库')
stock_in_quantity = FloatField(label='入库数量')
__all__ = [
'NumberResponse',
'NumberResponse', 'ProductionStockInRequest',
]

View file

@ -30,8 +30,8 @@ class ProductionOrderSerializer(BaseSerializer):
class Meta:
model = ProductionOrder
read_only_fields = ['id', 'sales_order_number', 'sales_goods_items', 'remain_quantity', 'goods_number',
'goods_name', 'quantity_produced', 'remain_quantity', 'status', 'status_display',
read_only_fields = ['id', 'sales_order_number', 'sales_goods_items', 'remain_quantity', 'stock_in_quantity',
'goods_number', 'goods_name', 'quantity_produced', 'remain_quantity', 'status', 'status_display',
'creator', 'creator_name', 'create_time']
fields = ['number', 'is_related', 'sales_order', 'goods', 'total_quantity',
'start_time', 'end_time', *read_only_fields]

View file

@ -8,6 +8,8 @@ from apps.production.permissions import *
from apps.production.filters import *
from apps.production.schemas import *
from apps.production.models import *
from apps.stock_in.models import *
from apps.data.models import *
class ProductionOrderViewSet(ModelViewSet):
@ -72,6 +74,42 @@ class ProductionOrderViewSet(ModelViewSet):
serializer = ProductionOrderSerializer(instance=instance)
return Response(data=serializer.data, status=status.HTTP_200_OK)
@extend_schema(request=ProductionStockInRequest, responses={200: ProductionOrderSerializer})
@action(detail=True, methods=['post'])
def stock_in(self, request, *args, **kwargs):
"""入库"""
request_serializer = ProductionStockInRequest(data=request.data)
request_serializer.is_valid(raise_exception=True)
validated_data = request_serializer.validated_data
warehouse_id, stock_in_quantity = validated_data['warehouse'], validated_data['stock_in_quantity']
if not (warehouse := Warehouse.objects.filter(id=warehouse_id, team=self.team).first()):
raise ValidationError(f'仓库不存在')
production_order = self.get_object()
if production_order.status != ProductionOrder.Status.IN_PROGRESS:
raise ValidationError(f'工单{production_order.number}{production_order.get_status_display()}, 无法入库')
# 创建入库单据
stock_in_order_number = StockInOrder.get_number(team=self.team)
stock_in_order = StockInOrder.objects.create(
number=stock_in_order_number, warehouse=warehouse, type=StockInOrder.Type.PRODUCTION,
production_order=production_order, total_quantity=stock_in_quantity, remain_quantity=stock_in_quantity,
creator=self.user, team=self.team)
# 创建入库产品
StockInGoods.objects.create(
stock_in_order=stock_in_order, goods=production_order.goods, stock_in_quantity=stock_in_quantity,
remain_quantity=stock_in_quantity, team=self.team)
# 同步生产单据
production_order.stock_in_quantity = NP.plus(production_order.stock_in_quantity, stock_in_quantity)
production_order.save(update_fields=['stock_in_quantity'])
serializer = ProductionOrderSerializer(instance=production_order)
return Response(data=serializer.data, status=status.HTTP_200_OK)
class ProductionRecordViewSet(BaseViewSet, ListModelMixin, RetrieveModelMixin, CreateModelMixin):
"""生产记录"""

View file

@ -11,6 +11,7 @@ class StockInOrder(Model):
PURCHASE = ('purchase', '采购')
SALES_RETURN = ('sales_return', '销售退货')
STOCK_TRANSFER = ('stock_transfer', '调拨')
PRODUCTION = ('production', '生产')
number = CharField(max_length=32, verbose_name='编号')
warehouse = ForeignKey('data.Warehouse', on_delete=PROTECT,
@ -22,6 +23,8 @@ class StockInOrder(Model):
related_name='stock_in_order', verbose_name='销售退货单据')
stock_transfer_order = OneToOneField('stock_transfer.StockTransferOrder', on_delete=CASCADE, null=True,
related_name='stock_in_order', verbose_name='调拨单据')
production_order = OneToOneField('production.ProductionOrder', on_delete=CASCADE, null=True,
related_name='stock_in_order', verbose_name='生产单据')
total_quantity = FloatField(verbose_name='入库总数')
remain_quantity = FloatField(default=0, verbose_name='入库剩余数量')
is_completed = BooleanField(default=False, verbose_name='入库完成状态')

View file

@ -30,10 +30,12 @@ class StockInOrderSerializer(BaseSerializer):
warehouse_name = CharField(source='warehouse.name', read_only=True, label='仓库名称')
type_display = CharField(source='get_type_display', read_only=True, label='入库类型')
purchase_order_number = CharField(source='purchase_order.number', read_only=True, label='采购单据编号')
sales_return_order_number = CharField(source='sales_return_order.number',
read_only=True, label='销售退货单据编号')
stock_transfer_order_number = CharField(source='stock_transfer_order.number',
read_only=True, label='调拨单据编号')
sales_return_order_number = CharField(
source='sales_return_order.number', read_only=True, label='销售退货单据编号')
stock_transfer_order_number = CharField(
source='stock_transfer_order.number', read_only=True, label='调拨单据编号')
production_order_number = CharField(
source='production_order.number', read_only=True, label='生产单据编号')
creator_name = CharField(source='creator.name', read_only=True, label='创建人名称')
stock_in_goods_items = StockInGoodsItemSerializer(
source='stock_in_goods_set', many=True, label='入库产品Item')
@ -43,6 +45,7 @@ class StockInOrderSerializer(BaseSerializer):
fields = ['id', 'number', 'warehouse', 'warehouse_number', 'warehouse_name', 'type',
'type_display', 'purchase_order', 'purchase_order_number', 'sales_return_order',
'sales_return_order_number', 'stock_transfer_order', 'stock_transfer_order_number',
'production_order', 'production_order_number',
'total_quantity', 'remain_quantity', 'is_completed', 'is_void', 'creator',
'creator_name', 'create_time', 'stock_in_goods_items']

View file

@ -38,7 +38,7 @@ instance.interceptors.response.use(
console.error("Request error:", error.response);
if (error.response.status >= 500) {
message.error(T("服务器错误"));
message.error("服务器错误");
return Promise.reject(error);
}
@ -62,7 +62,7 @@ instance.interceptors.response.use(
.catch((error) => {
if (error.response.status == 401) {
redirectLogin();
message.error(T("令牌过期, 请重新登录"));
message.error("令牌过期, 请重新登录");
}
return Promise.reject(error);
})

View file

@ -0,0 +1,119 @@
<template>
<div>
<a-modal v-model="visible" title="新增入库" :confirmLoading="loading" :maskClosable="false" @cancel="cancel" @ok="confirm">
<div slot="title">{{ form.id ? "编辑生产计划" : "新增生产计划" }}</div>
<div>
<a-form-model ref="form" :model="dataForm" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-model-item prop="number" label="生产单号">
<a-input v-model="dataForm.number" />
</a-form-model-item>
<a-form-model-item prop="is_related" label="类型">
<a-radio-group v-model="dataForm.is_related" button-style="solid" @change="switchType">
<a-radio-button :value="false">按库存生产</a-radio-button>
<a-radio-button :value="true">按订单生产</a-radio-button>
</a-radio-group>
</a-form-model-item>
<a-form-model-item v-if="!dataForm.is_related" prop="goods" label="产品">
<goods-select v-model="dataForm.goods" :defaultItem="{ ...dataForm }" />
</a-form-model-item>
<a-form-model-item v-if="dataForm.is_related" prop="sales_order" label="销售单">
<sales-order-select
v-model="dataForm.sales_order"
:defaultItem="{ ...dataForm }"
@select="
(item) => {
dataForm.sales_goods_items = item.sales_goods_items;
dataForm.goods = undefined;
}
"
/>
</a-form-model-item>
<a-form-model-item v-if="dataForm.is_related" prop="goods" label="产品">
<a-select v-model="dataForm.goods" style="width: 100%">
<a-select-option v-for="item in dataForm.sales_goods_items" :key="item.goods" :value="item.goods">
{{ item.goods_name }}
</a-select-option>
</a-select>
</a-form-model-item>
<a-form-model-item prop="total_quantity" label="计划数量">
<a-input-number v-model="dataForm.total_quantity" style="width: 100%" />
</a-form-model-item>
<a-form-model-item prop="start_time" label="开始时间">
<a-date-picker
v-model="dataForm.start_time"
placeholder="请选择时间"
valueFormat="YYYY-MM-DD HH:mm:ss"
show-time
style="width: 100%"
/>
</a-form-model-item>
<a-form-model-item prop="end_time" label="结束时间">
<a-date-picker
v-model="dataForm.end_time"
placeholder="请选择时间"
valueFormat="YYYY-MM-DD HH:mm:ss"
show-time
style="width: 100%"
/>
</a-form-model-item>
</a-form-model>
</div>
</a-modal>
</div>
</template>
<script>
import { productionOrderCreate, productionOrderUpdate, productionOrderNumber } from "@/api/production";
export default {
components: {},
props: ["visible"],
model: { prop: "visible", event: "cancel" },
data() {
return {
rules: {
number: [
{ required: true, message: "请输入编号", trigger: "change" },
{ max: 32, message: "超出最大长度 (32)", trigger: "change" },
],
goods: [{ required: true, message: "请选择生产产品", trigger: "change" }],
total_quantity: [{ required: true, message: "请输入计划数量", trigger: "change" }],
},
loading: false,
dataForm: {},
};
},
methods: {
confirm() {
this.$refs.form.validate((valid) => {
if (valid) {
this.loading = true;
let func = this.dataForm.id ? productionOrderUpdate : productionOrderCreate;
func(this.dataForm)
.then((data) => {
this.$message.success(this.form.id ? "修改成功" : "新增成功");
this.$emit(this.form.id ? "update" : "create", data);
this.cancel();
})
.finally(() => {
this.loading = false;
});
}
});
},
cancel() {
this.$emit("cancel", false);
this.$refs.form.resetFields();
},
},
watch: {
visible(value) {
if (value) {
this.dataForm = {};
}
},
},
};
</script>
<style scoped></style>

View file

@ -2,20 +2,18 @@
<div>
<a-card title="生产计划">
<a-row :gutter="[12, 8]">
<a-col :span="24" style="width: 256px;">
<a-col :span="24" style="width: 256px">
<a-range-picker @change="onChangePicker" />
</a-col>
<a-col :span="24" style="width: 200px;">
<a-col :span="24" style="width: 200px">
<a-input v-model="searchForm.search" placeholder="生产单号, 销售单号" allowClear @pressEnter="search" />
</a-col>
<a-col :span="24" style="width: 84px;">
<a-col :span="24" style="width: 84px">
<a-button type="primary" icon="search" @click="search">查询</a-button>
</a-col>
<div style="margin-bottom: 12px; float: right">
<a-button type="primary" icon="plus" style="margin: 0 8px" @click="openCreateModal({})">
新增生产计划
</a-button>
<a-button type="primary" icon="plus" style="margin: 0 8px" @click="openCreateModal({})"> 新增生产计划 </a-button>
</div>
</a-row>
@ -38,6 +36,7 @@
<a-popconfirm v-if="item.status == 'in_progress'" title="确定关闭吗?" @confirm="close(item)">
<a-button type="primary">关闭工单</a-button>
</a-popconfirm>
<a-button type="primary" @click="stockInModalVisible = true">入库</a-button>
<a-popconfirm v-if="item.status == 'in_plan'" title="确定删除吗?" @confirm="destroy(item)">
<a-button type="danger">删除</a-button>
</a-popconfirm>
@ -48,20 +47,17 @@
</a-card>
<form-modal v-model="visible" :form="targetItem" @create="create" @update="update" />
<StockInModal v-model="stockInModalVisible" @update="update" />
</div>
</template>
<script>
import {
productionOrderList,
productionOrderDelete,
productionOrderIssue,
productionOrderClose,
} from "@/api/production";
import { productionOrderList, productionOrderDelete, productionOrderIssue, productionOrderClose } from "@/api/production";
export default {
components: {
FormModal: () => import("./FormModal.vue"),
StockInModal: () => import("./StockInModal"),
},
data() {
return {
@ -131,6 +127,8 @@ export default {
],
visible: false,
targetItem: {},
stockInModalVisible: false,
};
},
methods: {

View file

@ -1,8 +1,21 @@
<template>
<div>
<a-card title="入库通知单详情">
<a-button slot="extra" type="primary" style="margin-right: 8px;" ghost v-print="'#printContent'"> <a-icon type="printer" />打印</a-button>
<a-button slot="extra" type="primary" ghost @click="() => { this.$router.go(-1); }"> <a-icon type="left" />返回</a-button>
<a-button slot="extra" type="primary" style="margin-right: 8px" ghost v-print="'#printContent'">
<a-icon type="printer" />打印</a-button
>
<a-button
slot="extra"
type="primary"
ghost
@click="
() => {
this.$router.go(-1);
}
"
>
<a-icon type="left" />返回</a-button
>
<section id="printContent">
<a-spin :spinning="loading">
<img id="barcode" style="float: right" />
@ -16,26 +29,22 @@
<a-descriptions-item label="仓库">
{{ info.warehouse_name }}
</a-descriptions-item>
<a-descriptions-item :label="info.type === 'purchase' ? '采购单据' : (info.type === 'sales_return' ? '销售退货单据' : '调拨单据')">
{{ info.type === 'purchase' ? info.purchase_order_number : (info.type === 'sales_return' ? info.sales_return_order_number : info.stock_transfer_order_number) }}
<a-descriptions-item v-if="info.type === 'purchase'" label="采购单据">
{{ info.purchase_order_number }}
</a-descriptions-item>
<!-- <a-descriptions-item label="处理日期">
{{ info.handle_time }}
<a-descriptions-item v-if="info.type === 'sales_return'" label="销售退货单据">
{{ info.sales_return_order_number }}
</a-descriptions-item>
<a-descriptions-item label="其他费用">
{{ info.other_amount }}
<a-descriptions-item v-if="info.type === 'stock_transfer'" label="调拨单据">
{{ info.stock_transfer_order_number }}
</a-descriptions-item>
<a-descriptions-item v-if="info.type === 'production'" label="生产单据">
{{ info.production_order_number }}
</a-descriptions-item>
<a-descriptions-item label="备注">
{{ info.remark }}
</a-descriptions-item> -->
</a-descriptions>
<a-divider orientation="left" style="margin-top: 30px;">产品信息</a-divider>
<a-table
rowKey="id"
size="middle"
:columns="columns"
:data-source="info.stock_in_goods_items"
:pagination="false" />
<a-divider orientation="left" style="margin-top: 30px">产品信息</a-divider>
<a-table rowKey="id" size="middle" :columns="columns" :data-source="info.stock_in_goods_items" :pagination="false" />
</a-spin>
</section>
</a-card>
@ -43,9 +52,9 @@
</template>
<script>
import { stockInOrderDetail } from '@/api/warehouse'
import JsBarcode from 'jsbarcode'
import NP from 'number-precision'
import { stockInOrderDetail } from "@/api/warehouse";
import JsBarcode from "jsbarcode";
import NP from "number-precision";
export default {
data() {
@ -55,48 +64,48 @@
info: {},
columns: [
{
title: '序号',
dataIndex: 'index',
key: 'index',
title: "序号",
dataIndex: "index",
key: "index",
width: 45,
customRender: (value, item, index) => {
return item.isTotal ? '合计' : (index + 1)
return item.isTotal ? "合计" : index + 1;
},
},
{
title: '产品名称',
dataIndex: 'goods_name',
key: 'goods_name',
title: "产品名称",
dataIndex: "goods_name",
key: "goods_name",
width: 150,
},
{
title: '产品编号',
dataIndex: 'goods_number',
key: 'goods_number',
title: "产品编号",
dataIndex: "goods_number",
key: "goods_number",
width: 150,
},
{
title: '单位',
dataIndex: 'unit_name',
key: 'unit_name',
title: "单位",
dataIndex: "unit_name",
key: "unit_name",
width: 80,
},
{
title: '入库总数',
dataIndex: 'stock_in_quantity',
key: 'stock_in_quantity',
title: "入库总数",
dataIndex: "stock_in_quantity",
key: "stock_in_quantity",
width: 120,
},
{
title: '入库剩余数量',
dataIndex: 'remain_quantity',
key: 'remain_quantity',
title: "入库剩余数量",
dataIndex: "remain_quantity",
key: "remain_quantity",
width: 120,
},
{
title: '保质期天数',
dataIndex: 'shelf_life_days',
key: 'shelf_life_days',
title: "保质期天数",
dataIndex: "shelf_life_days",
key: "shelf_life_days",
width: 120,
},
// {
@ -111,7 +120,7 @@
// },
// }
],
}
};
},
created() {
this.initData();
@ -119,26 +128,28 @@
methods: {
getJsBarcode(number) {
JsBarcode("#barcode", number, {
lineColor: '#000',
lineColor: "#000",
width: 2,
height: 40,
displayValue: true
displayValue: true,
});
},
initData() {
this.loading = true;
stockInOrderDetail({ id: this.$route.query.id }).then(data => {
stockInOrderDetail({ id: this.$route.query.id })
.then((data) => {
this.info = data;
this.info.stock_in_goods_items = [
...this.info.stock_in_goods_items,
{
id: '-1',
id: "-1",
isTotal: true,
stock_in_quantity: this.info.total_quantity,
},
];
this.getJsBarcode(data.number)
}).finally(() => {
this.getJsBarcode(data.number);
})
.finally(() => {
this.loading = false;
});
},
@ -146,7 +157,6 @@
mounted() {
this.initData();
},
}
};
</script>
<style>
</style>
<style></style>

View file