perubahan denomuretor on pos sale
This commit is contained in:
File diff suppressed because one or more lines are too long
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.38.5"
|
"dart.flutterSdkPath": ".fvm/versions/3.38.5",
|
||||||
|
"cmake.ignoreCMakeListsMissing": true
|
||||||
}
|
}
|
||||||
@@ -14,3 +14,8 @@ A few resources to get you started if this is your first Flutter project:
|
|||||||
For help getting started with Flutter, view our
|
For help getting started with Flutter, view our
|
||||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||||
samples, guidance on mobile development, and a full API reference.
|
samples, guidance on mobile development, and a full API reference.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#run on web to all ip
|
||||||
|
flutter run -d web-server --web-hostname 0.0.0.0 --web-port 8080
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -55,6 +55,18 @@ class _AddPartyState extends State<AddParty> {
|
|||||||
final GlobalKey<FormState> _formKay = GlobalKey();
|
final GlobalKey<FormState> _formKay = GlobalKey();
|
||||||
FocusNode focusNode = FocusNode();
|
FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
|
/// Normalizes Indonesian phone numbers to local format (0xx...)
|
||||||
|
/// Handles: +6289..., 6289..., 0896.2792.1934, spaces, etc.
|
||||||
|
String _normalizePhone(String input) {
|
||||||
|
// Remove all non-digit characters (dots, spaces, dashes, +)
|
||||||
|
String digits = input.replaceAll(RegExp(r'[^\d]'), '');
|
||||||
|
// Strip country code: +62 or 62 at the start
|
||||||
|
if (digits.startsWith('62')) {
|
||||||
|
digits = '0' + digits.substring(2);
|
||||||
|
}
|
||||||
|
return digits;
|
||||||
|
}
|
||||||
|
|
||||||
List<Country> _countries = [];
|
List<Country> _countries = [];
|
||||||
Country? _selectedBillingCountry;
|
Country? _selectedBillingCountry;
|
||||||
Country? _selectedShippingCountry;
|
Country? _selectedShippingCountry;
|
||||||
@@ -152,11 +164,39 @@ class _AddPartyState extends State<AddParty> {
|
|||||||
TextFormField(
|
TextFormField(
|
||||||
controller: phoneController,
|
controller: phoneController,
|
||||||
keyboardType: TextInputType.phone,
|
keyboardType: TextInputType.phone,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
onChanged: (value) {
|
||||||
|
final normalized = _normalizePhone(value);
|
||||||
|
if (normalized != value) {
|
||||||
|
phoneController.value = TextEditingValue(
|
||||||
|
text: normalized,
|
||||||
|
selection: TextSelection.collapsed(offset: normalized.length),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return lang.S.of(context).pleaseEnterAValidPhoneNumber;
|
return lang.S.of(context).pleaseEnterAValidPhoneNumber;
|
||||||
}
|
}
|
||||||
|
final digits = value.replaceAll(RegExp(r'[^\d]'), '');
|
||||||
|
if (digits.length < 11 || digits.length > 13) {
|
||||||
|
// Show popup after frame
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
title: Text('Invalid Phone Number'),
|
||||||
|
content: Text('Phone number must be between 11 and 13 digits.'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
|
child: Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return 'Invalid length';
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -878,6 +918,28 @@ class _AddPartyState extends State<AddParty> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate phone number length (11-13 digits)
|
||||||
|
final phoneDigits = phoneController.text.replaceAll(RegExp(r'[^\d]'), '');
|
||||||
|
if (phoneDigits.isNotEmpty && (phoneDigits.length < 11 || phoneDigits.length > 13)) {
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
title: const Text('Invalid Number'),
|
||||||
|
content: const Text(
|
||||||
|
'Phone number must be between 11 and 13 digits.\n'
|
||||||
|
'Example: 08123456789 (11 digits) or 081234567890 (12 digits).',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
num parseOrZero(String? input) {
|
num parseOrZero(String? input) {
|
||||||
if (input == null || input.isEmpty) return 0;
|
if (input == null || input.isEmpty) return 0;
|
||||||
return num.tryParse(input) ?? 0;
|
return num.tryParse(input) ?? 0;
|
||||||
|
|||||||
@@ -364,6 +364,7 @@ class _DueCollectionScreenState extends State<DueCollectionScreen> {
|
|||||||
showWalletOption: true, // Configure as needed
|
showWalletOption: true, // Configure as needed
|
||||||
showChequeOption: (widget.customerModel.type != 'Supplier'), // Configure as needed
|
showChequeOption: (widget.customerModel.type != 'Supplier'), // Configure as needed
|
||||||
totalAmountController: paidText,
|
totalAmountController: paidText,
|
||||||
|
totalDueAmount: dueAmount,
|
||||||
onPaymentListChanged: () {},
|
onPaymentListChanged: () {},
|
||||||
),
|
),
|
||||||
const Divider(height: 20),
|
const Divider(height: 20),
|
||||||
|
|||||||
@@ -1001,6 +1001,7 @@ class AddSalesScreenState extends ConsumerState<AddAndUpdatePurchaseScreen> {
|
|||||||
key: paymentWidgetKey,
|
key: paymentWidgetKey,
|
||||||
showWalletOption: true,
|
showWalletOption: true,
|
||||||
totalAmountController: recevedAmountController,
|
totalAmountController: recevedAmountController,
|
||||||
|
totalDueAmount: providerData.totalPayableAmount,
|
||||||
showChequeOption: false,
|
showChequeOption: false,
|
||||||
initialTransactions: widget.transitionModel?.transactions,
|
initialTransactions: widget.transitionModel?.transactions,
|
||||||
onPaymentListChanged: () {
|
onPaymentListChanged: () {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import '../../Const/api_config.dart';
|
|||||||
import '../../GlobalComponents/glonal_popup.dart';
|
import '../../GlobalComponents/glonal_popup.dart';
|
||||||
import '../../Repository/API/future_invoice.dart';
|
import '../../Repository/API/future_invoice.dart';
|
||||||
import '../../constant.dart';
|
import '../../constant.dart';
|
||||||
import '../../currency.dart';
|
|
||||||
import '../../model/add_to_cart_model.dart';
|
import '../../model/add_to_cart_model.dart';
|
||||||
import '../../model/sale_transaction_model.dart';
|
import '../../model/sale_transaction_model.dart';
|
||||||
import '../../widgets/multipal payment mathods/multi_payment_widget.dart';
|
import '../../widgets/multipal payment mathods/multi_payment_widget.dart';
|
||||||
@@ -35,6 +34,16 @@ import '../invoice_details/sales_invoice_details_screen.dart';
|
|||||||
import '../vat_&_tax/model/vat_model.dart';
|
import '../vat_&_tax/model/vat_model.dart';
|
||||||
import '../vat_&_tax/provider/text_repo.dart';
|
import '../vat_&_tax/provider/text_repo.dart';
|
||||||
|
|
||||||
|
/// Format a number as Indonesian Rupiah for display only.
|
||||||
|
/// Example: 12500.75 → "Rp 12.500"
|
||||||
|
String _formatRupiah(num value) {
|
||||||
|
final intStr = value.round().toString().replaceAllMapped(
|
||||||
|
RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
|
||||||
|
(m) => '${m[1]}.',
|
||||||
|
);
|
||||||
|
return 'Rp $intStr';
|
||||||
|
}
|
||||||
|
|
||||||
class AddSalesScreen extends ConsumerStatefulWidget {
|
class AddSalesScreen extends ConsumerStatefulWidget {
|
||||||
AddSalesScreen({
|
AddSalesScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -70,6 +79,11 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// Pre-populate phone from selected customer
|
||||||
|
if (widget.customerModel?.phone != null) {
|
||||||
|
phoneController.text = widget.customerModel!.phone!;
|
||||||
|
}
|
||||||
|
|
||||||
// Listener for Received Amount Controller to calculate prices
|
// Listener for Received Amount Controller to calculate prices
|
||||||
recevedAmountController.addListener(() {
|
recevedAmountController.addListener(() {
|
||||||
final cart = ref.read(cartNotifier);
|
final cart = ref.read(cartNotifier);
|
||||||
@@ -296,7 +310,9 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text(lang.S.of(context).dueAmount),
|
Text(lang.S.of(context).dueAmount),
|
||||||
Text(
|
Text(
|
||||||
widget.customerModel?.due == null ? '$currency 0' : '$currency${widget.customerModel?.due}',
|
widget.customerModel?.due == null
|
||||||
|
? _formatRupiah(0)
|
||||||
|
: _formatRupiah(widget.customerModel!.due ?? 0),
|
||||||
style: const TextStyle(color: Color(0xFFFF8C34)),
|
style: const TextStyle(color: Color(0xFFFF8C34)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -304,7 +320,10 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
AppTextField(
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: AppTextField(
|
||||||
textFieldType: TextFieldType.NAME,
|
textFieldType: TextFieldType.NAME,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
initialValue: widget.customerModel?.name ?? 'Guest',
|
initialValue: widget.customerModel?.name ?? 'Guest',
|
||||||
@@ -314,13 +333,13 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Visibility(
|
),
|
||||||
visible: widget.customerModel == null,
|
const SizedBox(width: 16),
|
||||||
child: Padding(
|
Expanded(
|
||||||
padding: const EdgeInsets.only(top: 20.0),
|
|
||||||
child: AppTextField(
|
child: AppTextField(
|
||||||
controller: phoneController,
|
controller: phoneController,
|
||||||
textFieldType: TextFieldType.PHONE,
|
textFieldType: TextFieldType.PHONE,
|
||||||
|
readOnly: widget.customerModel != null,
|
||||||
decoration: kInputDecoration.copyWith(
|
decoration: kInputDecoration.copyWith(
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||||
labelText: lang.S.of(context).customerPhoneNumber,
|
labelText: lang.S.of(context).customerPhoneNumber,
|
||||||
@@ -328,6 +347,7 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -385,7 +405,7 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'$currency${formatPointNumber(providerData.totalAmount)}',
|
_formatRupiah(providerData.totalAmount),
|
||||||
style: _theme.textTheme.titleSmall?.copyWith(
|
style: _theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
@@ -584,30 +604,17 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
|
|
||||||
const SizedBox(width: 10),*/
|
const SizedBox(width: 10),*/
|
||||||
|
|
||||||
// VAT Amount Input Field
|
// VAT Amount Display (readOnly — show as Rupiah)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 30,
|
height: 30,
|
||||||
width: 100,
|
width: 100,
|
||||||
child: TextFormField(
|
child: Align(
|
||||||
controller: providerData.vatAmountController,
|
alignment: Alignment.centerRight,
|
||||||
|
child: Text(
|
||||||
|
_formatRupiah(num.tryParse(providerData.vatAmountController.text) ?? 0),
|
||||||
style: _theme.textTheme.titleSmall,
|
style: _theme.textTheme.titleSmall,
|
||||||
readOnly: true,
|
|
||||||
onChanged: (value) => providerData.calculateDiscount(
|
|
||||||
value: value,
|
|
||||||
selectedTaxType: discountType.toString(),
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: '0',
|
|
||||||
hintStyle: _theme.textTheme.titleSmall?.copyWith(
|
|
||||||
color: kPeraColor,
|
|
||||||
),
|
),
|
||||||
border: const UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
|
|
||||||
enabledBorder: const UnderlineInputBorder(borderSide: BorderSide(color: kBorder)),
|
|
||||||
focusedBorder: const UnderlineInputBorder(),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 8),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -663,7 +670,7 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
formatPointNumber(providerData.actualTotalAmount),
|
_formatRupiah(providerData.actualTotalAmount),
|
||||||
style: _theme.textTheme.titleSmall?.copyWith(
|
style: _theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
@@ -690,7 +697,7 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
formatPointNumber(providerData.roundingAmount),
|
_formatRupiah(providerData.roundingAmount),
|
||||||
style: _theme.textTheme.titleSmall?.copyWith(
|
style: _theme.textTheme.titleSmall?.copyWith(
|
||||||
color: kPeraColor,
|
color: kPeraColor,
|
||||||
),
|
),
|
||||||
@@ -710,7 +717,7 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
formatPointNumber(providerData.totalPayableAmount),
|
_formatRupiah(providerData.totalPayableAmount),
|
||||||
style: _theme.textTheme.titleSmall?.copyWith(
|
style: _theme.textTheme.titleSmall?.copyWith(
|
||||||
color: kPeraColor,
|
color: kPeraColor,
|
||||||
),
|
),
|
||||||
@@ -734,12 +741,29 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
color: kPeraColor,
|
color: kPeraColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
// Received Amount — show Rupiah format when readOnly, numeric input when editable
|
||||||
|
Builder(builder: (ctx) {
|
||||||
|
final isReadOnly = (paymentWidgetKey.currentState?.getPaymentEntries().length ?? 1) > 1;
|
||||||
|
if (isReadOnly) {
|
||||||
|
return SizedBox(
|
||||||
|
width: context.width() / 4,
|
||||||
|
height: 30,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Text(
|
||||||
|
_formatRupiah(num.tryParse(recevedAmountController.text) ?? 0),
|
||||||
|
style: _theme.textTheme.titleSmall,
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return SizedBox(
|
||||||
width: context.width() / 4,
|
width: context.width() / 4,
|
||||||
height: 30,
|
height: 30,
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: recevedAmountController,
|
controller: recevedAmountController,
|
||||||
readOnly: (paymentWidgetKey.currentState?.getPaymentEntries().length ?? 1) > 1,
|
readOnly: false,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: _theme.textTheme.titleSmall,
|
style: _theme.textTheme.titleSmall,
|
||||||
@@ -754,7 +778,8 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
contentPadding: EdgeInsets.symmetric(horizontal: 0, vertical: 8),
|
contentPadding: EdgeInsets.symmetric(horizontal: 0, vertical: 8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -774,7 +799,7 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
formatPointNumber(providerData.changeAmount),
|
_formatRupiah(providerData.changeAmount),
|
||||||
style: _theme.textTheme.titleSmall?.copyWith(
|
style: _theme.textTheme.titleSmall?.copyWith(
|
||||||
color: kPeraColor,
|
color: kPeraColor,
|
||||||
),
|
),
|
||||||
@@ -800,7 +825,7 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
formatPointNumber(providerData.dueAmount),
|
_formatRupiah(providerData.dueAmount),
|
||||||
style: _theme.textTheme.titleSmall?.copyWith(
|
style: _theme.textTheme.titleSmall?.copyWith(
|
||||||
color: kPeraColor,
|
color: kPeraColor,
|
||||||
),
|
),
|
||||||
@@ -820,6 +845,7 @@ class AddSalesScreenState extends ConsumerState<AddSalesScreen> {
|
|||||||
key: paymentWidgetKey,
|
key: paymentWidgetKey,
|
||||||
showWalletOption: true,
|
showWalletOption: true,
|
||||||
totalAmountController: recevedAmountController,
|
totalAmountController: recevedAmountController,
|
||||||
|
totalDueAmount: providerData.totalPayableAmount,
|
||||||
showChequeOption: true,
|
showChequeOption: true,
|
||||||
showCashDenomination: true,
|
showCashDenomination: true,
|
||||||
initialTransactions: widget.transitionModel?.transactions,
|
initialTransactions: widget.transitionModel?.transactions,
|
||||||
|
|||||||
@@ -4,9 +4,18 @@ import 'package:hugeicons/hugeicons.dart';
|
|||||||
import 'package:mobile_pos/Screens/Sales/provider/sales_cart_provider.dart';
|
import 'package:mobile_pos/Screens/Sales/provider/sales_cart_provider.dart';
|
||||||
import 'package:mobile_pos/Screens/Sales/sales_add_to_cart_sales_widget.dart';
|
import 'package:mobile_pos/Screens/Sales/sales_add_to_cart_sales_widget.dart';
|
||||||
import 'package:mobile_pos/constant.dart';
|
import 'package:mobile_pos/constant.dart';
|
||||||
import 'package:mobile_pos/currency.dart';
|
|
||||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||||
|
|
||||||
|
/// Format a number as Indonesian Rupiah for display only.
|
||||||
|
/// Example: 12500.75 → "Rp 12.500"
|
||||||
|
String _formatRupiah(num value) {
|
||||||
|
final intStr = value.round().toString().replaceAllMapped(
|
||||||
|
RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
|
||||||
|
(m) => '${m[1]}.',
|
||||||
|
);
|
||||||
|
return 'Rp $intStr';
|
||||||
|
}
|
||||||
|
|
||||||
class SalesCartListWidget extends ConsumerWidget {
|
class SalesCartListWidget extends ConsumerWidget {
|
||||||
const SalesCartListWidget({super.key});
|
const SalesCartListWidget({super.key});
|
||||||
|
|
||||||
@@ -137,7 +146,7 @@ class SalesCartListWidget extends ConsumerWidget {
|
|||||||
|
|
||||||
// 3. Qty Order
|
// 3. Qty Order
|
||||||
Text(
|
Text(
|
||||||
formatPointNumber(quantity),
|
formatPointNumber(quantity, addComma: true),
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -158,7 +167,7 @@ class SalesCartListWidget extends ConsumerWidget {
|
|||||||
|
|
||||||
// 5. Value Original
|
// 5. Value Original
|
||||||
Text(
|
Text(
|
||||||
'$currency${formatPointNumber(originalTotal)}',
|
_formatRupiah(originalTotal),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: kPeraColor,
|
color: kPeraColor,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -169,7 +178,7 @@ class SalesCartListWidget extends ConsumerWidget {
|
|||||||
// 6. Value VAT/Tax
|
// 6. Value VAT/Tax
|
||||||
if (itemVAT > 0) ...[
|
if (itemVAT > 0) ...[
|
||||||
Text(
|
Text(
|
||||||
'VAT: $currency${formatPointNumber(itemVAT)}',
|
'VAT: ${_formatRupiah(itemVAT)}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: kPeraColor,
|
color: kPeraColor,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
@@ -180,7 +189,7 @@ class SalesCartListWidget extends ConsumerWidget {
|
|||||||
|
|
||||||
// 7. Sum (Value Original + VAT/Tax)
|
// 7. Sum (Value Original + VAT/Tax)
|
||||||
Text(
|
Text(
|
||||||
'$currency${formatPointNumber(finalTotal)}',
|
_formatRupiah(finalTotal),
|
||||||
style: _theme.textTheme.titleSmall?.copyWith(
|
style: _theme.textTheme.titleSmall?.copyWith(
|
||||||
color: kMainColor,
|
color: kMainColor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ class _ProductCardState extends State<ProductCard> {
|
|||||||
),
|
),
|
||||||
// if (permissionService.hasPermission(Permit.salesPriceView.value))
|
// if (permissionService.hasPermission(Permit.salesPriceView.value))
|
||||||
Text(
|
Text(
|
||||||
'$currency${widget.productPrice}',
|
'$currency ${formatPointNumber(widget.productPrice, addComma: true)}',
|
||||||
style: theme.textTheme.titleMedium!.copyWith(fontSize: 18),
|
style: theme.textTheme.titleMedium!.copyWith(fontSize: 18),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import '../../Const/api_config.dart';
|
|||||||
import '../../GlobalComponents/glonal_popup.dart';
|
import '../../GlobalComponents/glonal_popup.dart';
|
||||||
import '../../constant.dart' as mainConstant;
|
import '../../constant.dart' as mainConstant;
|
||||||
import '../../constant.dart';
|
import '../../constant.dart';
|
||||||
import '../../currency.dart';
|
|
||||||
import '../../model/business_info_model.dart' as binfo;
|
import '../../model/business_info_model.dart' as binfo;
|
||||||
import '../../model/sale_transaction_model.dart';
|
import '../../model/sale_transaction_model.dart';
|
||||||
import '../../thermal priting invoices/model/print_transaction_model.dart';
|
import '../../thermal priting invoices/model/print_transaction_model.dart';
|
||||||
@@ -28,6 +27,16 @@ import '../language/language_provider.dart';
|
|||||||
import '../../Provider/product_provider.dart';
|
import '../../Provider/product_provider.dart';
|
||||||
import '../Home/home.dart';
|
import '../Home/home.dart';
|
||||||
|
|
||||||
|
/// Format a number as Indonesian Rupiah for display only.
|
||||||
|
/// Example: 12500.75 → "Rp 12.500"
|
||||||
|
String _formatRupiah(num value) {
|
||||||
|
final intStr = value.round().toString().replaceAllMapped(
|
||||||
|
RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
|
||||||
|
(m) => '${m[1]}.',
|
||||||
|
);
|
||||||
|
return 'Rp $intStr';
|
||||||
|
}
|
||||||
|
|
||||||
class SalesInvoiceDetails extends StatefulWidget {
|
class SalesInvoiceDetails extends StatefulWidget {
|
||||||
const SalesInvoiceDetails({
|
const SalesInvoiceDetails({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -654,7 +663,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text(
|
child: Text(
|
||||||
'$currency${mainConstant.formatPointNumber(saleDetail.discount ?? 0, addComma: true)}',
|
_formatRupiah(saleDetail.discount ?? 0),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: _theme.textTheme.titleLarge?.copyWith(
|
style: _theme.textTheme.titleLarge?.copyWith(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -678,7 +687,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text(
|
child: Text(
|
||||||
'$currency${mainConstant.formatPointNumber(totalPrice, addComma: true)}',
|
_formatRupiah(totalPrice),
|
||||||
style: _theme.textTheme.bodyLarge?.copyWith(
|
style: _theme.textTheme.bodyLarge?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontSize: fontSizeForPrinter(widget.businessInfo.data?.invoiceSize),
|
fontSize: fontSizeForPrinter(widget.businessInfo.data?.invoiceSize),
|
||||||
@@ -702,7 +711,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
text: '${lang.S.of(context).subTotal} : ',
|
text: '${lang.S.of(context).subTotal} : ',
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '$currency${mainConstant.formatPointNumber(getTotalForOldInvoice())}',
|
text: _formatRupiah(getTotalForOldInvoice()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -726,11 +735,11 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '$currency${mainConstant.formatPointNumber(
|
text: _formatRupiah(
|
||||||
(widget.saleTransaction.discountAmount ?? 0) +
|
(widget.saleTransaction.discountAmount ?? 0) +
|
||||||
getReturndDiscountAmount() +
|
getReturndDiscountAmount() +
|
||||||
getTotalItemDiscount(),
|
getTotalItemDiscount(),
|
||||||
)}',
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -754,7 +763,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '$currency${mainConstant.formatPointNumber(widget.saleTransaction.vatAmount ?? 0)}',
|
text: _formatRupiah(widget.saleTransaction.vatAmount ?? 0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -778,8 +787,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text: _formatRupiah(widget.saleTransaction.shippingCharge ?? 0),
|
||||||
'$currency${mainConstant.formatPointNumber(widget.saleTransaction.shippingCharge ?? 0)}',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -808,8 +816,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text: _formatRupiah(widget.saleTransaction.actualTotalAmount ?? 0),
|
||||||
'$currency${mainConstant.formatPointNumber(widget.saleTransaction.actualTotalAmount ?? 0)}',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -834,7 +841,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text:
|
||||||
'$currency${!(widget.saleTransaction.roundingAmount?.isNegative ?? true) ? '+' : ''}${mainConstant.formatPointNumber(widget.saleTransaction.roundingAmount ?? 0)}',
|
'${!(widget.saleTransaction.roundingAmount?.isNegative ?? true) ? '+' : ''}${_formatRupiah(widget.saleTransaction.roundingAmount ?? 0)}',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -857,8 +864,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
text: '${lang.S.of(context).totalAmount} : ',
|
text: '${lang.S.of(context).totalAmount} : ',
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text: _formatRupiah(getTotalReturndAmount() + (widget.saleTransaction.totalAmount ?? 0)),
|
||||||
'$currency${mainConstant.formatPointNumber(getTotalReturndAmount() + (widget.saleTransaction.totalAmount ?? 0))}',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1023,7 +1029,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text(
|
child: Text(
|
||||||
'$currency${(widget.saleTransaction.salesReturns?[i].salesReturnDetails?[detailIndex].returnAmount ?? 0)}',
|
_formatRupiah(widget.saleTransaction.salesReturns?[i].salesReturnDetails?[detailIndex].returnAmount ?? 0),
|
||||||
textAlign: TextAlign.end,
|
textAlign: TextAlign.end,
|
||||||
style: _theme.textTheme.bodyLarge?.copyWith(
|
style: _theme.textTheme.bodyLarge?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -1048,7 +1054,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
text: '${lang.S.of(context).totalReturnAmount} : ',
|
text: '${lang.S.of(context).totalReturnAmount} : ',
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: '$currency${mainConstant.formatPointNumber(getTotalReturndAmount())}',
|
text: _formatRupiah(getTotalReturndAmount()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1069,8 +1075,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text: _formatRupiah(widget.saleTransaction.totalAmount ?? 0),
|
||||||
'$currency${mainConstant.formatPointNumber(widget.saleTransaction.totalAmount ?? 0)}',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1090,8 +1095,11 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
text: '${lang.S.of(context).receivedAmount} : ',
|
text: '${lang.S.of(context).receivedAmount} : ',
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text: _formatRupiah(
|
||||||
'$currency${mainConstant.formatPointNumber(((widget.saleTransaction.totalAmount ?? 0) - (widget.saleTransaction.dueAmount ?? 0)) + (widget.saleTransaction.changeAmount ?? 0))}',
|
((widget.saleTransaction.totalAmount ?? 0) -
|
||||||
|
(widget.saleTransaction.dueAmount ?? 0)) +
|
||||||
|
(widget.saleTransaction.changeAmount ?? 0),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1113,8 +1121,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
text: '${lang.S.of(context).due} : ',
|
text: '${lang.S.of(context).due} : ',
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text: _formatRupiah(widget.saleTransaction.dueAmount ?? 0),
|
||||||
'$currency${mainConstant.formatPointNumber(widget.saleTransaction.dueAmount ?? 0)}',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1136,8 +1143,7 @@ class _SalesInvoiceDetailsState extends State<SalesInvoiceDetails> {
|
|||||||
text: '${_lang.changeAmount} : ',
|
text: '${_lang.changeAmount} : ',
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text: _formatRupiah(widget.saleTransaction.changeAmount ?? 0),
|
||||||
'$currency${mainConstant.formatPointNumber(widget.saleTransaction.changeAmount ?? 0)}',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
import 'package:mobile_pos/constant.dart';
|
import 'package:mobile_pos/constant.dart';
|
||||||
import 'package:mobile_pos/currency.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../../Const/api_config.dart';
|
import '../../Const/api_config.dart';
|
||||||
@@ -33,6 +32,16 @@ import '../../model/business_info_model.dart';
|
|||||||
import '../Home/home.dart';
|
import '../Home/home.dart';
|
||||||
import '../../Provider/profile_provider.dart';
|
import '../../Provider/profile_provider.dart';
|
||||||
|
|
||||||
|
/// Format a number as Indonesian Rupiah for display only.
|
||||||
|
/// Example: 12500.75 → "Rp 12.500"
|
||||||
|
String formatRupiah(num value) {
|
||||||
|
final intStr = value.round().toString().replaceAllMapped(
|
||||||
|
RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
|
||||||
|
(m) => '${m[1]}.',
|
||||||
|
);
|
||||||
|
return 'Rp $intStr';
|
||||||
|
}
|
||||||
|
|
||||||
class PosSaleScreen extends ConsumerStatefulWidget {
|
class PosSaleScreen extends ConsumerStatefulWidget {
|
||||||
const PosSaleScreen({super.key});
|
const PosSaleScreen({super.key});
|
||||||
|
|
||||||
@@ -272,7 +281,7 @@ class _PosSaleScreenState extends ConsumerState<PosSaleScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'(${providerData.cartItemList.length} Items): $currency${providerData.getTotalAmount()}',
|
'(${providerData.cartItemList.length} Items): ${formatRupiah(num.tryParse(providerData.getTotalAmount().toString()) ?? 0)}',
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 14),
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -900,26 +909,26 @@ class _PosSaleScreenState extends ConsumerState<PosSaleScreen> {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
() {
|
() {
|
||||||
// Update: Price Display Logic
|
// Display-only: format as Indonesian Rupiah (Rp x.xxx.xxx,xx)
|
||||||
if (isCombo) {
|
if (isCombo) {
|
||||||
return '$currency${product.productSalePrice ?? 0}';
|
return formatRupiah(product.productSalePrice ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final customerType = selectedCustomer?.type ?? '';
|
final customerType = selectedCustomer?.type ?? '';
|
||||||
final stock = product.stocks?.isNotEmpty == true ? product.stocks!.last : null;
|
final stock = product.stocks?.isNotEmpty == true ? product.stocks!.last : null;
|
||||||
|
|
||||||
if (stock == null) return '$currency${0.00}';
|
if (stock == null) return formatRupiah(0);
|
||||||
|
|
||||||
if (customerType.contains('Retailer')) {
|
if (customerType.contains('Retailer')) {
|
||||||
return '$currency${stock.productSalePrice ?? 0}';
|
return formatRupiah(stock.productSalePrice ?? 0);
|
||||||
} else if (customerType.contains('Dealer')) {
|
} else if (customerType.contains('Dealer')) {
|
||||||
return '$currency${stock.productDealerPrice ?? 0}';
|
return formatRupiah(stock.productDealerPrice ?? 0);
|
||||||
} else if (customerType.contains('Wholesaler')) {
|
} else if (customerType.contains('Wholesaler')) {
|
||||||
return '$currency${stock.productWholeSalePrice ?? 0}';
|
return formatRupiah(stock.productWholeSalePrice ?? 0);
|
||||||
} else if (customerType.contains('Supplier')) {
|
} else if (customerType.contains('Supplier')) {
|
||||||
return '$currency${stock.productPurchasePrice ?? 0}';
|
return formatRupiah(stock.productPurchasePrice ?? 0);
|
||||||
} else {
|
} else {
|
||||||
return '$currency${stock.productSalePrice ?? 0}';
|
return formatRupiah(stock.productSalePrice ?? 0);
|
||||||
}
|
}
|
||||||
}(),
|
}(),
|
||||||
style: _theme.textTheme.titleSmall?.copyWith(
|
style: _theme.textTheme.titleSmall?.copyWith(
|
||||||
@@ -1113,7 +1122,7 @@ class _PosSaleScreenState extends ConsumerState<PosSaleScreen> {
|
|||||||
|
|
||||||
// 5. Value Original
|
// 5. Value Original
|
||||||
Text(
|
Text(
|
||||||
'$currency${originalTotal.toStringAsFixed(2)}',
|
formatRupiah(originalTotal),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: kSubPeraColor,
|
color: kSubPeraColor,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -1124,7 +1133,7 @@ class _PosSaleScreenState extends ConsumerState<PosSaleScreen> {
|
|||||||
// 6. Value VAT/Tax
|
// 6. Value VAT/Tax
|
||||||
if (itemVAT > 0) ...[
|
if (itemVAT > 0) ...[
|
||||||
Text(
|
Text(
|
||||||
'VAT: $currency${itemVAT.toStringAsFixed(2)}',
|
'VAT: ${formatRupiah(itemVAT)}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: kSubPeraColor,
|
color: kSubPeraColor,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
@@ -1135,7 +1144,7 @@ class _PosSaleScreenState extends ConsumerState<PosSaleScreen> {
|
|||||||
|
|
||||||
// 7. Sum (Value Original + VAT/Tax)
|
// 7. Sum (Value Original + VAT/Tax)
|
||||||
Text(
|
Text(
|
||||||
'$currency${finalTotal.toStringAsFixed(2)}',
|
formatRupiah(finalTotal),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: kMainColor,
|
color: kMainColor,
|
||||||
@@ -1198,7 +1207,7 @@ class _PosSaleScreenState extends ConsumerState<PosSaleScreen> {
|
|||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Text(
|
child: Text(
|
||||||
'$currency${providerData.getTotalAmount()}',
|
formatRupiah(num.tryParse(providerData.getTotalAmount().toString()) ?? 0),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:pdf/widgets.dart' as pw;
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
|
|
||||||
import 'generated/l10n.dart' as lang;
|
import 'generated/l10n.dart' as lang;
|
||||||
|
import 'currency.dart';
|
||||||
|
|
||||||
const kMainColor = Color(0xffC52127);
|
const kMainColor = Color(0xffC52127);
|
||||||
const kMainColor100 = Color(0xffFDE7F2);
|
const kMainColor100 = Color(0xffFDE7F2);
|
||||||
@@ -49,7 +50,7 @@ const String onboard3 = 'images/onbord3.png';
|
|||||||
const String logo = 'images/logokulakpos.png';
|
const String logo = 'images/logokulakpos.png';
|
||||||
const String appsName = 'KULAKPOS';
|
const String appsName = 'KULAKPOS';
|
||||||
const String companyWebsite = 'https://kulakpos.id';
|
const String companyWebsite = 'https://kulakpos.id';
|
||||||
const String companyName = 'KULAKPOS';
|
const String companyName = 'ONE TEKNOLOGI';
|
||||||
|
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
|
|
||||||
@@ -202,19 +203,41 @@ Map<String, String> languageMap = {
|
|||||||
'Chinese': 'zh',
|
'Chinese': 'zh',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
String getLocaleFromCurrency() {
|
||||||
|
final loweredName = currencyName.toLowerCase();
|
||||||
|
if (loweredName.contains('rupiah') || currency.toLowerCase() == 'rp' || currency == 'IDR') {
|
||||||
|
return 'id_ID';
|
||||||
|
} else if (loweredName.contains('rupee') || currency == '₹' || currency.toLowerCase() == 'rs' || currency == 'INR') {
|
||||||
|
return 'en_IN';
|
||||||
|
} else if (loweredName.contains('euro') || currency == '€' || currency == 'EUR') {
|
||||||
|
return 'de_DE';
|
||||||
|
}
|
||||||
|
return 'en_US';
|
||||||
|
}
|
||||||
|
|
||||||
String formatPointNumber(num value, {bool addComma = false}) {
|
String formatPointNumber(num value, {bool addComma = false}) {
|
||||||
String formatted;
|
String formatted;
|
||||||
|
|
||||||
if (value % 1 == 0) {
|
// Always use 2 decimal places for consistent display
|
||||||
formatted = value.toInt().toString();
|
|
||||||
} else {
|
|
||||||
formatted = value.toStringAsFixed(2);
|
formatted = value.toStringAsFixed(2);
|
||||||
}
|
|
||||||
|
|
||||||
if (addComma) {
|
if (addComma) {
|
||||||
// NumberFormat from intl package is used here
|
try {
|
||||||
final formatter = NumberFormat.decimalPattern();
|
final formatter = NumberFormat.currency(locale: getLocaleFromCurrency(), symbol: '', decimalDigits: 2);
|
||||||
formatted = formatter.format(num.parse(formatted));
|
formatted = formatter.format(value).trim();
|
||||||
|
} catch (e) {
|
||||||
|
final fallback = NumberFormat.decimalPattern(getLocaleFromCurrency());
|
||||||
|
fallback.minimumFractionDigits = 2;
|
||||||
|
fallback.maximumFractionDigits = 2;
|
||||||
|
try {
|
||||||
|
formatted = fallback.format(value);
|
||||||
|
} catch (e2) {
|
||||||
|
final fallbackUs = NumberFormat.decimalPattern();
|
||||||
|
fallbackUs.minimumFractionDigits = 2;
|
||||||
|
fallbackUs.maximumFractionDigits = 2;
|
||||||
|
formatted = fallbackUs.format(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatted;
|
return formatted;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:image/image.dart' as img;
|
|||||||
import 'package:mobile_pos/Const/api_config.dart';
|
import 'package:mobile_pos/Const/api_config.dart';
|
||||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||||
import 'package:mobile_pos/thermal%20priting%20invoices/model/print_transaction_model.dart';
|
import 'package:mobile_pos/thermal%20priting%20invoices/model/print_transaction_model.dart';
|
||||||
|
import 'package:mobile_pos/constant.dart';
|
||||||
import '../../thermer/thermer.dart' as thermer;
|
import '../../thermer/thermer.dart' as thermer;
|
||||||
|
|
||||||
class DueThermalInvoiceTemplate {
|
class DueThermalInvoiceTemplate {
|
||||||
@@ -31,11 +32,6 @@ class DueThermalInvoiceTemplate {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String formatPointNumber(num number, {bool addComma = false}) {
|
|
||||||
if (addComma) return NumberFormat("#,###.##", "en_US").format(number);
|
|
||||||
return number.toStringAsFixed(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Main Generator ---
|
// --- Main Generator ---
|
||||||
|
|
||||||
Future<List<int>> get template async {
|
Future<List<int>> get template async {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import 'package:mobile_pos/Screens/Products/add%20product/modle/create_product_m
|
|||||||
import 'package:mobile_pos/Screens/Purchase/Model/purchase_transaction_model.dart';
|
import 'package:mobile_pos/Screens/Purchase/Model/purchase_transaction_model.dart';
|
||||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||||
import 'package:mobile_pos/thermal%20priting%20invoices/model/print_transaction_model.dart';
|
import 'package:mobile_pos/thermal%20priting%20invoices/model/print_transaction_model.dart';
|
||||||
|
import 'package:mobile_pos/constant.dart';
|
||||||
import '../../thermer/thermer.dart' as thermer;
|
import '../../thermer/thermer.dart' as thermer;
|
||||||
|
|
||||||
class PurchaseThermalInvoiceTemplate {
|
class PurchaseThermalInvoiceTemplate {
|
||||||
@@ -37,11 +37,6 @@ class PurchaseThermalInvoiceTemplate {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String formatPointNumber(num number, {bool addComma = false}) {
|
|
||||||
if (addComma) return NumberFormat("#,###.##", "en_US").format(number);
|
|
||||||
return number.toStringAsFixed(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Data Logic (Adapted from your provided code) ---
|
// --- Data Logic (Adapted from your provided code) ---
|
||||||
|
|
||||||
num _getProductPrice(num detailsId) {
|
num _getProductPrice(num detailsId) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:mobile_pos/Const/api_config.dart';
|
|||||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||||
import 'package:mobile_pos/model/business_info_model.dart';
|
import 'package:mobile_pos/model/business_info_model.dart';
|
||||||
import 'package:mobile_pos/model/sale_transaction_model.dart';
|
import 'package:mobile_pos/model/sale_transaction_model.dart';
|
||||||
|
import 'package:mobile_pos/constant.dart';
|
||||||
import '../../thermer/thermer.dart' as thermer;
|
import '../../thermer/thermer.dart' as thermer;
|
||||||
|
|
||||||
class SaleThermalInvoiceTemplate {
|
class SaleThermalInvoiceTemplate {
|
||||||
@@ -36,11 +37,6 @@ class SaleThermalInvoiceTemplate {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String formatPointNumber(num number, {bool addComma = false}) {
|
|
||||||
if (addComma) return NumberFormat("#,###.##", "en_US").format(number);
|
|
||||||
return number.toStringAsFixed(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Data Logic ---
|
// --- Data Logic ---
|
||||||
|
|
||||||
String _getProductName(num detailsId) {
|
String _getProductName(num detailsId) {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class MultiPaymentWidget extends ConsumerStatefulWidget {
|
|||||||
final bool hideAddButton;
|
final bool hideAddButton;
|
||||||
final bool disableDropdown;
|
final bool disableDropdown;
|
||||||
final bool showCashDenomination;
|
final bool showCashDenomination;
|
||||||
|
final num? totalDueAmount;
|
||||||
|
|
||||||
final VoidCallback? onPaymentListChanged;
|
final VoidCallback? onPaymentListChanged;
|
||||||
final List<PaymentsTransaction>? initialTransactions;
|
final List<PaymentsTransaction>? initialTransactions;
|
||||||
@@ -53,6 +54,7 @@ class MultiPaymentWidget extends ConsumerStatefulWidget {
|
|||||||
this.hideAddButton = false,
|
this.hideAddButton = false,
|
||||||
this.disableDropdown = false,
|
this.disableDropdown = false,
|
||||||
this.showCashDenomination = false,
|
this.showCashDenomination = false,
|
||||||
|
this.totalDueAmount,
|
||||||
this.onPaymentListChanged,
|
this.onPaymentListChanged,
|
||||||
this.initialTransactions,
|
this.initialTransactions,
|
||||||
});
|
});
|
||||||
@@ -135,6 +137,39 @@ class MultiPaymentWidgetState extends ConsumerState<MultiPaymentWidget> {
|
|||||||
_isSyncing = false;
|
_isSyncing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(MultiPaymentWidget oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (widget.totalDueAmount != oldWidget.totalDueAmount && widget.totalDueAmount != null && widget.totalDueAmount! > 0) {
|
||||||
|
if (_paymentEntries.length == 1) {
|
||||||
|
if (widget.totalDueAmount! % 1 == 0) {
|
||||||
|
_paymentEntries[0].amountController.text = widget.totalDueAmount!.toInt().toString();
|
||||||
|
} else {
|
||||||
|
_paymentEntries[0].amountController.text = widget.totalDueAmount!.toStringAsFixed(2);
|
||||||
|
}
|
||||||
|
_calculateTotalsFromPayments();
|
||||||
|
} else if (_paymentEntries.length > 1) {
|
||||||
|
// Adjust the last entry
|
||||||
|
double otherTotals = 0;
|
||||||
|
for (int i = 0; i < _paymentEntries.length - 1; i++) {
|
||||||
|
otherTotals += double.tryParse(_paymentEntries[i].amountController.text) ?? 0;
|
||||||
|
}
|
||||||
|
double remaining = widget.totalDueAmount!.toDouble() - otherTotals;
|
||||||
|
if (remaining > 0) {
|
||||||
|
if (remaining % 1 == 0) {
|
||||||
|
_paymentEntries.last.amountController.text = remaining.toInt().toString();
|
||||||
|
} else {
|
||||||
|
_paymentEntries.last.amountController.text = remaining.toStringAsFixed(2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_paymentEntries.last.amountController.text = '0';
|
||||||
|
}
|
||||||
|
_calculateTotalsFromPayments();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Listener for all payment amount fields
|
// Listener for all payment amount fields
|
||||||
void _calculateTotalsFromPayments() {
|
void _calculateTotalsFromPayments() {
|
||||||
if (_isSyncing) return;
|
if (_isSyncing) return;
|
||||||
@@ -290,6 +325,25 @@ class MultiPaymentWidgetState extends ConsumerState<MultiPaymentWidget> {
|
|||||||
: (value) {
|
: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
payment.type = value;
|
payment.type = value;
|
||||||
|
if (widget.totalDueAmount != null && widget.totalDueAmount! > 0) {
|
||||||
|
double otherTotals = 0;
|
||||||
|
for (var p in _paymentEntries) {
|
||||||
|
if (p != payment) {
|
||||||
|
otherTotals += double.tryParse(p.amountController.text) ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double remaining = widget.totalDueAmount!.toDouble() - otherTotals;
|
||||||
|
if (remaining > 0) {
|
||||||
|
if (remaining % 1 == 0) {
|
||||||
|
payment.amountController.text = remaining.toInt().toString();
|
||||||
|
} else {
|
||||||
|
payment.amountController.text = remaining.toStringAsFixed(2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
payment.amountController.text = '0';
|
||||||
|
}
|
||||||
|
_calculateTotalsFromPayments();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
|
|||||||
24
test_format.dart
Normal file
24
test_format.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
num value = 5600.0;
|
||||||
|
String formatted = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final formatter = NumberFormat.currency(locale: 'id_ID', symbol: '', decimalDigits: value % 1 == 0 ? 0 : 2);
|
||||||
|
formatted = formatter.format(value).trim();
|
||||||
|
print('currency: $formatted');
|
||||||
|
} catch (e) {
|
||||||
|
print('error currency: $e');
|
||||||
|
try {
|
||||||
|
final fallback = NumberFormat.decimalPattern('id_ID');
|
||||||
|
formatted = fallback.format(value);
|
||||||
|
print('decimalPattern: $formatted');
|
||||||
|
} catch(e2) {
|
||||||
|
print('error decimalPattern: $e2');
|
||||||
|
final fallbackUs = NumberFormat.decimalPattern();
|
||||||
|
formatted = fallbackUs.format(value);
|
||||||
|
print('fallbackUs: $formatted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user