mirror of
https://github.com/himool/HimoolERP.git
synced 2024-11-10 17:05:03 +08:00
feat: 生产管理
This commit is contained in:
parent
1f3c76e9f2
commit
b62456d9c1
5 changed files with 296 additions and 15 deletions
|
@ -46,10 +46,14 @@ class ProductionOrderSerializer(BaseSerializer):
|
|||
return super().validate(attrs)
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['remain_quantity'] = validated_data['total_quantity']
|
||||
validated_data['quantity_produced'] = 0
|
||||
validated_data['creator'] = self.user
|
||||
return super().create(validated_data)
|
||||
|
||||
def save(self, **kwargs):
|
||||
kwargs['remain_quantity'] = kwargs['total_quantity']
|
||||
return super().save(**kwargs)
|
||||
|
||||
|
||||
class ProductionRecordSerializer(BaseSerializer):
|
||||
"""生产记录"""
|
||||
|
|
109
frontend/src/components/GoodsSelect/index.vue
Normal file
109
frontend/src/components/GoodsSelect/index.vue
Normal file
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-select
|
||||
v-model="value"
|
||||
:placeholder="placeholder"
|
||||
allowClear
|
||||
show-search
|
||||
:disabled="disabled"
|
||||
:filter-option="false"
|
||||
:not-found-content="loading ? undefined : '暂无数据'"
|
||||
style="width: 100%;"
|
||||
@search="search"
|
||||
@change="change"
|
||||
@focus="focus"
|
||||
@popupScroll="scroll"
|
||||
>
|
||||
<div slot="notFoundContent" style="text-align: center;">
|
||||
<a-spin size="small" :spinning="loading" />
|
||||
</div>
|
||||
|
||||
<a-select-option v-for="item in options" :key="item.id" :value="item.id">
|
||||
{{ item.name }}
|
||||
</a-select-option>
|
||||
|
||||
<a-select-option v-if="!isloadedAll" key="loading" value="loading" disabled>
|
||||
<div style="text-align: center; height: 24px;">
|
||||
<a-spin size="small" :spinning="loading" />
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { goodsOption } from "@/api/option";
|
||||
|
||||
export default {
|
||||
props: ["value", "placeholder", "disabled", "defaultItem"],
|
||||
model: { prop: "value", event: "change" },
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
searchForm: { search: "", page: 1, is_active: true, page_size: 15 },
|
||||
totalRows: 0,
|
||||
loading: false,
|
||||
timeout: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
let items = [...this.items];
|
||||
if (this.defaultItem && !this.loading && (!this.searchForm.search || this.searchForm.search == "")) {
|
||||
if (this.defaultItem.goods && this.defaultItem.goods_name) {
|
||||
if (this.items.findIndex((item) => item.id == this.value) == -1) {
|
||||
items.splice(0, 0, { id: this.defaultItem, name: this.defaultItem.goods_name });
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
},
|
||||
isloadedAll() {
|
||||
return this.searchForm.page * this.searchForm.page_size >= this.totalRows;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
list() {
|
||||
if (this.searchForm.page === 1) {
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
goodsOption(this.searchForm)
|
||||
.then((data) => {
|
||||
this.totalRows = data.count;
|
||||
this.items.push(...data.results);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
change(value) {
|
||||
this.$emit("change", value);
|
||||
},
|
||||
focus() {
|
||||
this.searchForm.page = 1;
|
||||
this.list();
|
||||
},
|
||||
scroll({ target }) {
|
||||
if (!this.loading && target.scrollTop + target.offsetHeight === target.scrollHeight) {
|
||||
if (!this.isloadedAll) {
|
||||
this.searchForm.page += 1;
|
||||
this.list();
|
||||
}
|
||||
}
|
||||
},
|
||||
search(value) {
|
||||
this.searchForm.page = 1;
|
||||
this.searchForm.search = value;
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
this.timeout = setTimeout(this.list, 300);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
113
frontend/src/components/SalesOrderSelect/index.vue
Normal file
113
frontend/src/components/SalesOrderSelect/index.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-select
|
||||
v-model="value"
|
||||
:placeholder="placeholder"
|
||||
allowClear
|
||||
show-search
|
||||
:disabled="disabled"
|
||||
:filter-option="false"
|
||||
:not-found-content="loading ? undefined : '暂无数据'"
|
||||
style="width: 100%;"
|
||||
@search="search"
|
||||
@change="change"
|
||||
@focus="focus"
|
||||
@popupScroll="scroll"
|
||||
>
|
||||
<div slot="notFoundContent" style="text-align: center;">
|
||||
<a-spin size="small" :spinning="loading" />
|
||||
</div>
|
||||
|
||||
<a-select-option v-for="item in options" :key="item.id" :value="item.id">
|
||||
{{ item.number }}
|
||||
</a-select-option>
|
||||
|
||||
<a-select-option v-if="!isloadedAll" key="loading" value="loading" disabled>
|
||||
<div style="text-align: center; height: 24px;">
|
||||
<a-spin size="small" :spinning="loading" />
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saleOrdersOption } from "@/api/option";
|
||||
|
||||
export default {
|
||||
props: ["value", "placeholder", "disabled", "defaultItem"],
|
||||
model: { prop: "value", event: "change" },
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
searchForm: { search: "", page: 1, is_active: true, page_size: 15 },
|
||||
totalRows: 0,
|
||||
loading: false,
|
||||
timeout: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
options() {
|
||||
let items = [...this.items];
|
||||
if (this.defaultItem && !this.loading && (!this.searchForm.search || this.searchForm.search == "")) {
|
||||
if (this.defaultItem.sales_order && this.defaultItem.sales_order_number) {
|
||||
if (this.items.findIndex((item) => item.id == this.value) == -1) {
|
||||
items.splice(0, 0, { id: this.defaultItem, name: this.defaultItem.sales_order_number });
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
},
|
||||
isloadedAll() {
|
||||
return this.searchForm.page * this.searchForm.page_size >= this.totalRows;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
list() {
|
||||
if (this.searchForm.page === 1) {
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
saleOrdersOption(this.searchForm)
|
||||
.then((data) => {
|
||||
this.totalRows = data.count;
|
||||
this.items.push(...data.results);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
change(value) {
|
||||
this.$emit("change", value);
|
||||
let index = this.items.findIndex((item) => item.id == value);
|
||||
if (index != -1) {
|
||||
this.$emit("select", this.items[index]);
|
||||
}
|
||||
},
|
||||
focus() {
|
||||
this.searchForm.page = 1;
|
||||
this.list();
|
||||
},
|
||||
scroll({ target }) {
|
||||
if (!this.loading && target.scrollTop + target.offsetHeight === target.scrollHeight) {
|
||||
if (!this.isloadedAll) {
|
||||
this.searchForm.page += 1;
|
||||
this.list();
|
||||
}
|
||||
}
|
||||
},
|
||||
search(value) {
|
||||
this.searchForm.page = 1;
|
||||
this.searchForm.search = value;
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
this.timeout = setTimeout(this.list, 300);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -3,15 +3,53 @@
|
|||
<a-modal v-model="visible" :confirmLoading="loading" :maskClosable="false" @cancel="cancel" @ok="confirm">
|
||||
<div slot="title">{{ form.id ? "编辑生产计划" : "新增生产计划" }}</div>
|
||||
<div>
|
||||
<a-form-model ref="form" :model="form" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
|
||||
<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="form.is_related" button-style="solid">
|
||||
<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" />
|
||||
</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"
|
||||
@select="(item) => (goodsItems = item.sales_goods_items)"
|
||||
/>
|
||||
</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 goodsItems" :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>
|
||||
|
@ -19,22 +57,23 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
// import { clientCreate, clientUpdate } from "@/api/basicData";
|
||||
import { productionOrderCreate, productionOrderUpdate, productionOrderNumber } from "@/api/production";
|
||||
|
||||
export default {
|
||||
name: "FormModal",
|
||||
components: {
|
||||
GoodsSelect: () => import("@/components/GoodsSelect/index"),
|
||||
SalesOrderSelect: () => import("@/components/SalesOrderSelect/index"),
|
||||
},
|
||||
props: ["visible", "form"],
|
||||
model: { prop: "visible", event: "cancel" },
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
name: [{ required: true, message: "请输入名称", trigger: "change" }],
|
||||
number: [{ required: true, message: "请输入编号", trigger: "change" }],
|
||||
initial_arrears_amount: [
|
||||
{ pattern: new RegExp(/^\d{0,14}(?:\.\d{0,2})?$/), message: "初期欠款金额格式不正确", trigger: "change" },
|
||||
],
|
||||
},
|
||||
loading: false,
|
||||
dataForm: {},
|
||||
goodsItems: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -42,8 +81,8 @@ export default {
|
|||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
let func = this.form.id ? clientUpdate : clientCreate;
|
||||
func(this.form)
|
||||
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);
|
||||
|
@ -59,6 +98,23 @@ export default {
|
|||
this.$emit("cancel", false);
|
||||
this.$refs.form.resetFields();
|
||||
},
|
||||
switchType() {
|
||||
this.dataForm = { ...this.dataForm, sales_order: undefined, goods: undefined };
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(value) {
|
||||
if (value) {
|
||||
if (this.form.id) {
|
||||
this.dataForm = { ...this.form };
|
||||
} else {
|
||||
this.dataForm = { is_related: false };
|
||||
productionOrderNumber().then((data) => {
|
||||
this.dataForm = { ...this.dataForm, number: data.number };
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</a-col>
|
||||
|
||||
<div style="margin-bottom: 12px; float: right">
|
||||
<a-button type="primary" icon="plus" style="margin: 0 8px" @click="openCreateModal(defaultForm)">
|
||||
<a-button type="primary" icon="plus" style="margin: 0 8px" @click="openCreateModal({})">
|
||||
新增生产计划
|
||||
</a-button>
|
||||
</div>
|
||||
|
@ -128,7 +128,6 @@ export default {
|
|||
],
|
||||
visible: false,
|
||||
targetItem: {},
|
||||
defaultForm: { is_related: false },
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
Loading…
Reference in a new issue