Update: lib/screens/add_edit_product_screen.dart
This commit is contained in:
parent
697ed887f1
commit
c5ca9acdac
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../models/product.dart';
|
||||
import '../services/database_helper.dart';
|
||||
|
||||
|
@ -18,6 +19,30 @@ class _AddEditProductScreenState extends State<AddEditProductScreen> {
|
|||
late TextEditingController _quantityController;
|
||||
late TextEditingController _priceController;
|
||||
late TextEditingController _descriptionController;
|
||||
|
||||
late TextEditingController _imageUrlController;
|
||||
late TextEditingController _serialNumberController;
|
||||
late TextEditingController _modelController;
|
||||
late TextEditingController _tagsController;
|
||||
|
||||
late TextEditingController _costPriceController;
|
||||
late TextEditingController _taxRateController;
|
||||
late TextEditingController _discountController;
|
||||
late TextEditingController _profitMarginController;
|
||||
|
||||
late TextEditingController _unitOfMeasureController;
|
||||
late TextEditingController _locationController;
|
||||
late TextEditingController _minimumStockController;
|
||||
late TextEditingController _supplierController;
|
||||
|
||||
late TextEditingController _entryDateController;
|
||||
late TextEditingController _manufactureDateController;
|
||||
late TextEditingController _expirationDateController;
|
||||
late TextEditingController _statusController;
|
||||
|
||||
late TextEditingController _categoryController;
|
||||
late TextEditingController _brandController;
|
||||
|
||||
late DatabaseHelper _databaseHelper;
|
||||
|
||||
bool get isEditing => widget.product != null;
|
||||
|
@ -26,17 +51,37 @@ class _AddEditProductScreenState extends State<AddEditProductScreen> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
_databaseHelper = DatabaseHelper();
|
||||
|
||||
_nameController = TextEditingController(text: widget.product?.name);
|
||||
_codeController = TextEditingController(text: widget.product?.code);
|
||||
_quantityController = TextEditingController(
|
||||
text: widget.product?.quantity.toString(),
|
||||
);
|
||||
_priceController = TextEditingController(
|
||||
text: widget.product?.price.toString(),
|
||||
);
|
||||
_descriptionController = TextEditingController(
|
||||
text: widget.product?.description,
|
||||
);
|
||||
_descriptionController = TextEditingController(text: widget.product?.description);
|
||||
_imageUrlController = TextEditingController(text: widget.product?.imageUrl);
|
||||
_serialNumberController = TextEditingController(text: widget.product?.serialNumber);
|
||||
_modelController = TextEditingController(text: widget.product?.model);
|
||||
_tagsController = TextEditingController(text: widget.product?.tags?.join(', '));
|
||||
|
||||
_priceController = TextEditingController(text: widget.product?.price.toString());
|
||||
_costPriceController = TextEditingController(text: widget.product?.costPrice?.toString());
|
||||
_taxRateController = TextEditingController(text: widget.product?.taxRate?.toString());
|
||||
_discountController = TextEditingController(text: widget.product?.discount?.toString());
|
||||
_profitMarginController = TextEditingController(text: widget.product?.profitMargin?.toString());
|
||||
|
||||
_quantityController = TextEditingController(text: widget.product?.quantity.toString());
|
||||
_unitOfMeasureController = TextEditingController(text: widget.product?.unitOfMeasure);
|
||||
_locationController = TextEditingController(text: widget.product?.location);
|
||||
_minimumStockController = TextEditingController(text: widget.product?.minimumStock?.toString());
|
||||
_supplierController = TextEditingController(text: widget.product?.supplier);
|
||||
|
||||
_entryDateController = TextEditingController(
|
||||
text: widget.product?.entryDate != null ? DateFormat('yyyy-MM-dd').format(widget.product!.entryDate!) : '');
|
||||
_manufactureDateController = TextEditingController(
|
||||
text: widget.product?.manufactureDate != null ? DateFormat('yyyy-MM-dd').format(widget.product!.manufactureDate!) : '');
|
||||
_expirationDateController = TextEditingController(
|
||||
text: widget.product?.expirationDate != null ? DateFormat('yyyy-MM-dd').format(widget.product!.expirationDate!) : '');
|
||||
_statusController = TextEditingController(text: widget.product?.status);
|
||||
|
||||
_categoryController = TextEditingController(text: widget.product?.category);
|
||||
_brandController = TextEditingController(text: widget.product?.brand);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -46,9 +91,47 @@ class _AddEditProductScreenState extends State<AddEditProductScreen> {
|
|||
_quantityController.dispose();
|
||||
_priceController.dispose();
|
||||
_descriptionController.dispose();
|
||||
|
||||
_imageUrlController.dispose();
|
||||
_serialNumberController.dispose();
|
||||
_modelController.dispose();
|
||||
_tagsController.dispose();
|
||||
|
||||
_costPriceController.dispose();
|
||||
_taxRateController.dispose();
|
||||
_discountController.dispose();
|
||||
_profitMarginController.dispose();
|
||||
|
||||
_unitOfMeasureController.dispose();
|
||||
_locationController.dispose();
|
||||
_minimumStockController.dispose();
|
||||
_supplierController.dispose();
|
||||
|
||||
_entryDateController.dispose();
|
||||
_manufactureDateController.dispose();
|
||||
_expirationDateController.dispose();
|
||||
_statusController.dispose();
|
||||
|
||||
_categoryController.dispose();
|
||||
_brandController.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _selectDate(TextEditingController controller) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
controller.text = DateFormat('yyyy-MM-dd').format(picked);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveProduct() async {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
_formKey.currentState!.save();
|
||||
|
@ -57,12 +140,36 @@ class _AddEditProductScreenState extends State<AddEditProductScreen> {
|
|||
id: isEditing ? widget.product!.id : null,
|
||||
name: _nameController.text,
|
||||
code: _codeController.text,
|
||||
quantity: int.parse(_quantityController.text),
|
||||
description: _descriptionController.text.isEmpty ? null : _descriptionController.text,
|
||||
imageUrl: _imageUrlController.text.isEmpty ? null : _imageUrlController.text,
|
||||
serialNumber: _serialNumberController.text.isEmpty ? null : _serialNumberController.text,
|
||||
model: _modelController.text.isEmpty ? null : _modelController.text,
|
||||
tags: _tagsController.text.isEmpty ? null : _tagsController.text.split(',').map((e) => e.trim()).toList(),
|
||||
|
||||
price: double.parse(_priceController.text),
|
||||
description:
|
||||
_descriptionController.text.isEmpty
|
||||
? null
|
||||
: _descriptionController.text,
|
||||
costPrice: double.tryParse(_costPriceController.text),
|
||||
taxRate: double.tryParse(_taxRateController.text),
|
||||
discount: double.tryParse(_discountController.text),
|
||||
profitMargin: double.tryParse(_profitMarginController.text),
|
||||
|
||||
quantity: int.parse(_quantityController.text),
|
||||
unitOfMeasure: _unitOfMeasureController.text.isEmpty ? null : _unitOfMeasureController.text,
|
||||
location: _locationController.text.isEmpty ? null : _locationController.text,
|
||||
minimumStock: int.tryParse(_minimumStockController.text),
|
||||
supplier: _supplierController.text.isEmpty ? null : _supplierController.text,
|
||||
|
||||
entryDate: _entryDateController.text.isNotEmpty ? DateTime.parse(_entryDateController.text) : null,
|
||||
manufactureDate: _manufactureDateController.text.isNotEmpty ? DateTime.parse(_manufactureDateController.text) : null,
|
||||
expirationDate: _expirationDateController.text.isNotEmpty ? DateTime.parse(_expirationDateController.text) : null,
|
||||
status: _statusController.text.isEmpty ? null : _statusController.text,
|
||||
|
||||
category: _categoryController.text.isEmpty ? null : _categoryController.text,
|
||||
brand: _brandController.text.isEmpty ? null : _brandController.text,
|
||||
|
||||
createdBy: isEditing ? widget.product?.createdBy : null,
|
||||
createdAt: isEditing ? widget.product?.createdAt : null,
|
||||
lastModifiedBy: isEditing ? widget.product?.lastModifiedBy : null,
|
||||
lastModifiedAt: isEditing ? widget.product?.lastModifiedAt : null,
|
||||
);
|
||||
|
||||
if (isEditing) {
|
||||
|
@ -89,74 +196,182 @@ class _AddEditProductScreenState extends State<AddEditProductScreen> {
|
|||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(labelText: 'نام کالا'),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'لطفا نام کالا را وارد کنید';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TextFormField(
|
||||
controller: _codeController,
|
||||
decoration: const InputDecoration(labelText: 'کد کالا'),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'لطفا کد کالا را وارد کنید';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TextFormField(
|
||||
controller: _quantityController,
|
||||
decoration: const InputDecoration(labelText: 'تعداد'),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'لطفا تعداد را وارد کنید';
|
||||
}
|
||||
if (int.tryParse(value) == null) {
|
||||
return 'تعداد باید عدد باشد';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TextFormField(
|
||||
controller: _priceController,
|
||||
decoration: const InputDecoration(labelText: 'قیمت'),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'لطفا قیمت را وارد کنید';
|
||||
}
|
||||
if (double.tryParse(value) == null) {
|
||||
return 'قیمت باید عدد باشد';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TextFormField(
|
||||
controller: _descriptionController,
|
||||
decoration: const InputDecoration(
|
||||
_buildSectionTitle('مشخصات شناسایی و ظاهری'),
|
||||
_buildTextFormField(
|
||||
controller: _nameController,
|
||||
labelText: 'نام کالا',
|
||||
validator: (value) => value!.isEmpty ? 'لطفا نام کالا را وارد کنید' : null),
|
||||
_buildTextFormField(
|
||||
controller: _codeController,
|
||||
labelText: 'کد کالا',
|
||||
validator: (value) => value!.isEmpty ? 'لطفا کد کالا را وارد کنید' : null),
|
||||
_buildTextFormField(
|
||||
controller: _descriptionController,
|
||||
labelText: 'توضیحات (اختیاری)',
|
||||
),
|
||||
maxLines: 3,
|
||||
),
|
||||
maxLines: 3),
|
||||
_buildTextFormField(
|
||||
controller: _imageUrlController,
|
||||
labelText: 'آدرس عکس کالا (URL/مسیر)',
|
||||
keyboardType: TextInputType.url),
|
||||
_buildTextFormField(
|
||||
controller: _serialNumberController,
|
||||
labelText: 'شماره سریال (اختیاری)'),
|
||||
_buildTextFormField(
|
||||
controller: _modelController,
|
||||
labelText: 'شماره مدل (اختیاری)'),
|
||||
_buildTextFormField(
|
||||
controller: _tagsController,
|
||||
labelText: 'هشتگها (با کاما جدا کنید)',
|
||||
hintText: 'مثال: لوازم، برقی، آشپزخانه'),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
_buildSectionTitle('اطلاعات مالی و تجاری'),
|
||||
_buildTextFormField(
|
||||
controller: _priceController,
|
||||
labelText: 'قیمت فروش (تومان)',
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) => value!.isEmpty || double.tryParse(value) == null ? 'لطفا قیمت معتبر وارد کنید' : null),
|
||||
_buildTextFormField(
|
||||
controller: _costPriceController,
|
||||
labelText: 'قیمت خرید / بهای تمام شده (اختیاری)',
|
||||
keyboardType: TextInputType.number),
|
||||
_buildTextFormField(
|
||||
controller: _taxRateController,
|
||||
labelText: 'نرخ مالیات (مثلاً 0.09 برای 9%) (اختیاری)',
|
||||
keyboardType: TextInputType.number),
|
||||
_buildTextFormField(
|
||||
controller: _discountController,
|
||||
labelText: 'میزان تخفیف (درصد یا مبلغ) (اختیاری)',
|
||||
keyboardType: TextInputType.number),
|
||||
_buildTextFormField(
|
||||
controller: _profitMarginController,
|
||||
labelText: 'درصد سود مورد انتظار (اختیاری)',
|
||||
keyboardType: TextInputType.number),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
_buildSectionTitle('موجودی و لجستیک'),
|
||||
_buildTextFormField(
|
||||
controller: _quantityController,
|
||||
labelText: 'تعداد',
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) => value!.isEmpty || int.tryParse(value) == null ? 'لطفا تعداد معتبر وارد کنید' : null),
|
||||
_buildTextFormField(
|
||||
controller: _unitOfMeasureController,
|
||||
labelText: 'واحد اندازهگیری (مثلاً عدد، کیلوگرم) (اختیاری)'),
|
||||
_buildTextFormField(
|
||||
controller: _locationController,
|
||||
labelText: 'مکان فیزیکی در انبار (اختیاری)'),
|
||||
_buildTextFormField(
|
||||
controller: _minimumStockController,
|
||||
labelText: 'حداقل موجودی برای سفارش مجدد (اختیاری)',
|
||||
keyboardType: TextInputType.number),
|
||||
_buildTextFormField(
|
||||
controller: _supplierController,
|
||||
labelText: 'تامینکننده (اختیاری)'),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
_buildSectionTitle('تاریخگذاری و وضعیت'),
|
||||
_buildDateField(
|
||||
controller: _entryDateController,
|
||||
labelText: 'تاریخ ورود/ثبت (اختیاری)'),
|
||||
_buildDateField(
|
||||
controller: _manufactureDateController,
|
||||
labelText: 'تاریخ تولید (اختیاری)'),
|
||||
_buildDateField(
|
||||
controller: _expirationDateController,
|
||||
labelText: 'تاریخ انقضا (اختیاری)'),
|
||||
_buildTextFormField(
|
||||
controller: _statusController,
|
||||
labelText: 'وضعیت کالا (مثلاً موجود، ناموجود) (اختیاری)'),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
_buildSectionTitle('دستهبندی و طبقهبندی'),
|
||||
_buildTextFormField(
|
||||
controller: _categoryController,
|
||||
labelText: 'دسته بندی (مثلاً الکترونیک) (اختیاری)'),
|
||||
_buildTextFormField(
|
||||
controller: _brandController,
|
||||
labelText: 'برند/شرکت سازنده (اختیاری)'),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
ElevatedButton(
|
||||
onPressed: _saveProduct,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
textStyle: const TextStyle(fontSize: 18),
|
||||
),
|
||||
child: Text(isEditing ? 'ذخیره تغییرات' : 'افزودن کالا'),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextFormField({
|
||||
required TextEditingController controller,
|
||||
required String labelText,
|
||||
String? Function(String?)? validator,
|
||||
TextInputType keyboardType = TextInputType.text,
|
||||
int maxLines = 1,
|
||||
String? hintText,
|
||||
bool readOnly = false,
|
||||
}) {
|
||||
return Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: labelText,
|
||||
hintText: hintText,
|
||||
border: const OutlineInputBorder(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||
),
|
||||
keyboardType: keyboardType,
|
||||
maxLines: maxLines,
|
||||
validator: validator,
|
||||
readOnly: readOnly,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateField({
|
||||
required TextEditingController controller,
|
||||
required String labelText,
|
||||
}) {
|
||||
return Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: controller,
|
||||
decoration: InputDecoration(
|
||||
labelText: labelText,
|
||||
border: const OutlineInputBorder(),
|
||||
suffixIcon: const Icon(Icons.calendar_today),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||
),
|
||||
readOnly: true,
|
||||
onTap: () => _selectDate(controller),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionTitle(String title) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10, top: 10),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blueGrey,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue