ScanListCell.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. //
  2. // ScanListCell.m
  3. // HMLG Scan Order
  4. //
  5. // Created by Rui Zhang on 3/30/22.
  6. // Copyright © 2022 United Software Applications, Inc. All rights reserved.
  7. //
  8. #import "ScanListCell.h"
  9. #import "RASingleton.h"
  10. #import "OLDataProvider.h"
  11. #import "ActiveViewController.h"
  12. #import "RAConvertor.h"
  13. #import "RAUtils.h"
  14. #import "AppDelegate.h"
  15. #import "RADataProvider.h"
  16. // 表格行高(header 行 + data 行各一行)
  17. static const CGFloat kTableRowHeight = 24.0;
  18. // 单元格左右内边距
  19. static const CGFloat kCellHPadding = 20;
  20. // 列最小宽度
  21. static const CGFloat kMinColumnWidth = 125;
  22. @implementation ScanListCell
  23. - (void)awakeFromNib {
  24. [super awakeFromNib];
  25. // priceScrollView
  26. self.priceScrollView.showsVerticalScrollIndicator = NO;
  27. self.priceScrollView.showsHorizontalScrollIndicator = YES;
  28. self.priceScrollView.alwaysBounceVertical = NO;
  29. self.priceScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  30. // 关闭默认选中高亮,避免与购物车高亮冲突
  31. self.selectionStyle = UITableViewCellSelectionStyleNone;
  32. // qtyField 样式
  33. self.qtyField.text = @"1";
  34. // iPad 上 NumberPad 会浮动,改用 numbersAndPunctuation 让键盘固定在底部
  35. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  36. self.qtyField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
  37. // cell 内部过滤非数字,不依赖外部 delegate
  38. [self.qtyField addTarget:self
  39. action:@selector(qtyFieldDidChange:)
  40. forControlEvents:UIControlEventEditingChanged];
  41. } else {
  42. self.qtyField.keyboardType = UIKeyboardTypeNumberPad;
  43. }
  44. self.qtyField.textAlignment = NSTextAlignmentCenter;
  45. self.qtyField.borderStyle = UITextBorderStyleRoundedRect;
  46. self.qtyField.font = [UIFont systemFontOfSize:15.0];
  47. self.qtyField.layer.cornerRadius = 4.0;
  48. self.qtyField.layer.borderWidth = 1.0;
  49. self.qtyField.layer.borderColor = [UIColor systemGrayColor].CGColor;
  50. self.qtyField.layer.masksToBounds = YES;
  51. }
  52. - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
  53. [super setSelected:selected animated:animated];
  54. }
  55. // MARK: - Add to Cart
  56. - (IBAction)onAddToCart:(id)sender {
  57. // ── 读取用户输入数量,默认 1,不允许 <= 0 ──────────────────────────────
  58. int userQty = [self.qtyField.text intValue];
  59. if (userQty <= 0) userQty = 1;
  60. if (RASingleton.sharedInstance.scan_cart == nil) {
  61. NSMutableDictionary *cartTemplate = [OLDataProvider loadScanTemplate:@"scan_cart.json"];
  62. RASingleton.sharedInstance.scan_cart = cartTemplate;
  63. }
  64. if (RASingleton.sharedInstance.scan_cart[@"price_type"] == nil) {
  65. RASingleton.sharedInstance.scan_cart[@"price_type"] = @1;
  66. RASingleton.sharedInstance.price_type = 1;
  67. }
  68. NSMutableDictionary *section = [RASingleton.sharedInstance.scan_cart[@"section_0"] mutableCopy];
  69. int count = [section[@"count"] intValue];
  70. NSMutableDictionary *jitem = [self.modelJson mutableCopy];
  71. AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
  72. NSDictionary *price_group = [RADataProvider get_price_group:appDelegate.price_group];
  73. BOOL newitem = YES;
  74. for (int i = 0; i < count; i++) {
  75. NSMutableDictionary *litem = [section[[NSString stringWithFormat:@"item_%i", i]] mutableCopy];
  76. if ([litem[@"product_id"] isEqualToString:jitem[@"product_id"]]) {
  77. newitem = NO;
  78. // 用 userQty 累加
  79. litem[@"count"] = @([litem[@"count"] intValue] + userQty);
  80. litem[@"cuft"] = @([litem[@"count"] intValue] * [litem[@"unit_cuft"] doubleValue]);
  81. double discount = [litem[@"discount"] doubleValue];
  82. int qty = [litem[@"count"] intValue];
  83. double unit_price = [litem[@"unit_price"] doubleValue];
  84. litem[@"subtotal_price"] = [NSString stringWithFormat:@"%f", unit_price * qty * (1 - discount / 100.0)];
  85. section[[NSString stringWithFormat:@"item_%i", i]] = litem;
  86. break;
  87. }
  88. }
  89. if (newitem) {
  90. jitem[@"count"] = @(userQty); // 使用用户输入数量
  91. jitem[@"check"] = @(YES);
  92. jitem[@"cart_item_id"] = [NSUUID UUID].UUIDString;
  93. NSString *unit_price_key;
  94. if (RASingleton.sharedInstance.price_type == 0)
  95. unit_price_key = price_group[@"price_0"][@"name"];
  96. else if (_modelJson[@"special_price"])
  97. unit_price_key = price_group[@"price_2"][@"name"];
  98. else if (_modelJson[@"net_price"])
  99. unit_price_key = price_group[@"price_3"][@"name"];
  100. else
  101. unit_price_key = price_group[@"price_1"][@"name"];
  102. NSString *unit_price = _modelJson[unit_price_key];
  103. if ([unit_price isEqualToString:@"N/A"])
  104. unit_price = @"0";
  105. else
  106. unit_price = [NSString stringWithFormat:@"%.2f", [unit_price doubleValue]];
  107. jitem[@"unit_price"] = unit_price;
  108. jitem[@"erp_unit_price"] = unit_price;
  109. // surcharge:key = 当前价格列名 + "_surcharge"
  110. NSString *surcharge_key = [unit_price_key stringByAppendingString:@"_surcharge"];
  111. NSString *unit_surcharge = _modelJson[surcharge_key];
  112. // if (unit_surcharge && unit_surcharge.length > 0 && ![unit_surcharge isEqualToString:@"N/A"])
  113. if (unit_surcharge )
  114. jitem[@"unit_surcharge"] = [NSString stringWithFormat:@"%.2f", [unit_surcharge doubleValue]];
  115. else
  116. jitem[@"unit_surcharge"] = @"0";
  117. double discount = [jitem[@"discount"] doubleValue];
  118. double dunit_price = [jitem[@"unit_price"] doubleValue];
  119. jitem[@"subtotal_price"] = [NSString stringWithFormat:@"%f", dunit_price * userQty * (1 - discount / 100.0)];
  120. section[[NSString stringWithFormat:@"item_%i", count]] = jitem;
  121. section[@"count"] = @(count + 1);
  122. }
  123. RASingleton.sharedInstance.scan_cart[@"section_0"] = section;
  124. [ActiveViewController Notify:@"CartViewController" Message:RA_NOTIFICATION_RELOAD_DATA];
  125. [OLDataProvider saveScanCart:RASingleton.sharedInstance.scan_cart];
  126. // ── 重置数量输入框 ────────────────────────────────────────────────────────
  127. self.qtyField.text = @"1";
  128. // ── 刷新当前列表高亮(Search 和 History VC 都会收到)────────────────────
  129. [[NSNotificationCenter defaultCenter] postNotificationName:@"ScanListShouldRefreshHighlight"
  130. object:nil];
  131. [RAUtils message_box:[NSString stringWithFormat:@"%@ added to cart", jitem[@"model"]]
  132. message:@"Successfully"
  133. completion:nil];
  134. }
  135. // MARK: - setModelJson
  136. - (void)setModelJson:(NSMutableDictionary *)modelJson {
  137. _modelJson = modelJson;
  138. AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
  139. NSDictionary *price_group = [RADataProvider get_price_group:appDelegate.price_group];
  140. #ifdef DEBUG
  141. NSLog(@"%@", [RAConvertor dict2string:modelJson]);
  142. #endif
  143. NSString *port = _modelJson[@"port"];
  144. if (port.length == 0) port = @"N/A";
  145. NSString *origin = _modelJson[@"origin"];
  146. if (origin.length == 0) origin = @"N/A";
  147. NSString *dimension = _modelJson[@"dimension"];
  148. if (dimension.length == 0) dimension = @"N/A";
  149. NSString *available = _modelJson[@"available"];
  150. if (available.length == 0) available = @"N/A";
  151. NSString *unit_cuft = _modelJson[@"unit_cuft"];
  152. if (unit_cuft.length == 0) {
  153. unit_cuft = @"N/A";
  154. } else {
  155. unit_cuft = [NSString stringWithFormat:@"%.2f", [_modelJson[@"unit_cuft"] doubleValue]];
  156. }
  157. _labelModel.text = _modelJson[@"model"];
  158. _labelDescription.text = _modelJson[@"description"];
  159. _labelDimension.text = dimension;
  160. // ── 重置数量输入框(cell 复用时还原为 1)────────────────────────────────
  161. self.qtyField.text = @"1";
  162. // ── 价格 flag 处理 ─────────────────────────────────────────────────────
  163. NSString *price1 = _modelJson[price_group[@"price_1"][@"name"]];
  164. NSString *price2 = _modelJson[price_group[@"price_2"][@"name"]];
  165. NSString *price3 = _modelJson[price_group[@"price_3"][@"name"]];
  166. if (![price1 isEqualToString:@"N/A"] && price1)
  167. price1 = [NSString stringWithFormat:@"%.2f", [price1 doubleValue]];
  168. if (![price2 isEqualToString:@"N/A"] && price2)
  169. price2 = [NSString stringWithFormat:@"%.2f", [price2 doubleValue]];
  170. if (![price3 isEqualToString:@"N/A"] && price3)
  171. price3 = [NSString stringWithFormat:@"%.2f", [price3 doubleValue]];
  172. if (price2 && ![price2 isEqualToString:@"N/A"])
  173. _modelJson[@"special_price"] = @YES;
  174. if ((price1 && ![price1 isEqualToString:@"N/A"] && [price1 isEqualToString:price3]) ||
  175. (price2 && ![price2 isEqualToString:@"N/A"] && [price2 isEqualToString:price3]))
  176. _modelJson[@"net_price"] = @NO;
  177. // ── 构建动态表格列 ──────────────────────────────────────────────────────
  178. NSMutableArray<NSDictionary *> *columns = [NSMutableArray array];
  179. [columns addObject:@{ @"header": @"CuFT", @"value": unit_cuft }];
  180. [columns addObject:@{ @"header": @"Origin", @"value": origin }];
  181. [columns addObject:@{ @"header": @"Port", @"value": port }];
  182. [columns addObject:@{ @"header": @"Available", @"value": available }];
  183. // 根据 price_count 遍历当前价格组的所有价格列
  184. int priceCount = [price_group[@"price_count"] intValue];
  185. for (int i = 0; i < priceCount; i++) {
  186. NSString *key = [NSString stringWithFormat:@"price_%d", i];
  187. NSDictionary *pg = price_group[key];
  188. if (!pg || !pg[@"display"]) continue;
  189. NSString *displayName = pg[@"display"];
  190. NSString *rawValue = _modelJson[pg[@"name"]];
  191. NSString *displayValue = @"";
  192. if (rawValue && ![rawValue isEqualToString:@"N/A"]) {
  193. double dval = [rawValue doubleValue];
  194. if (dval > 0)
  195. displayValue = [RAConvertor currencyNumber:(float)dval];
  196. }
  197. // price_2 固定为 Show Special,仅在 special_price 时显示数值
  198. if (i == 2 && ![_modelJson[@"special_price"] boolValue])
  199. displayValue = @"";
  200. [columns addObject:@{ @"header": displayName, @"value": displayValue }];
  201. }
  202. [self buildPriceTableWithColumns:columns];
  203. // ── 购物车高亮 ─────────────────────────────────────────────────────────────
  204. [self updateCartHighlight];
  205. // ── 更新 modelJson 供购物车使用 ────────────────────────────────────────
  206. NSString *cell_price;
  207. if (RASingleton.sharedInstance.price_type == 1)
  208. cell_price = _modelJson[price_group[@"price_3"][@"name"]];
  209. else
  210. cell_price = _modelJson[price_group[@"price_0"][@"name"]];
  211. NSString *mpack = _modelJson[@"stockUom"];
  212. if ([mpack isEqualToString:@"N/A"]) mpack = @"1";
  213. _modelJson[@"count"] = _modelJson[@"stockUom"];
  214. _modelJson[@"cuft"] = @([mpack intValue] * [unit_cuft doubleValue]);
  215. _modelJson[@"subtotal_price"] = @([mpack intValue] * [cell_price doubleValue]);
  216. }
  217. // MARK: - 购物车高亮
  218. /**
  219. 检查当前 modelJson 对应的商品是否已在购物车中。
  220. 在购物车中:浅绿背景;不在:恢复默认背景。
  221. 在 setModelJson: 末尾调用,也可在外部主动调用(如加入购物车后刷新列表时)。
  222. */
  223. - (void)updateCartHighlight {
  224. NSString *productId = _modelJson[@"product_id"];
  225. BOOL inCart = NO;
  226. NSDictionary *section = RASingleton.sharedInstance.scan_cart[@"section_0"];
  227. if (section && productId.length > 0) {
  228. int count = [section[@"count"] intValue];
  229. for (int i = 0; i < count; i++) {
  230. NSDictionary *item = section[[NSString stringWithFormat:@"item_%i", i]];
  231. if ([item[@"product_id"] isEqualToString:productId]) {
  232. inCart = YES;
  233. break;
  234. }
  235. }
  236. }
  237. UIColor *highlightColor = inCart
  238. ? [[UIColor systemGreenColor] colorWithAlphaComponent:0.12]
  239. : [UIColor systemBackgroundColor];
  240. self.contentView.backgroundColor = highlightColor;
  241. self.backgroundColor = highlightColor;
  242. }
  243. // MARK: - 动态表格构建
  244. - (void)buildPriceTableWithColumns:(NSArray<NSDictionary *> *)columns {
  245. for (UIView *v in self.priceScrollView.subviews)
  246. [v removeFromSuperview];
  247. if (columns.count == 0) {
  248. self.priceScrollView.contentSize = CGSizeZero;
  249. return;
  250. }
  251. UIFont *font = [UIFont systemFontOfSize:15.0];
  252. UIColor *borderColor = UIColor.labelColor;
  253. CGFloat totalWidth = 0;
  254. NSMutableArray<NSNumber *> *colWidths = [NSMutableArray arrayWithCapacity:columns.count];
  255. for (NSDictionary *col in columns) {
  256. NSString *header = col[@"header"] ?: @"";
  257. NSString *value = col[@"value"] ?: @"";
  258. NSDictionary *attrs = @{ NSFontAttributeName: font };
  259. CGFloat hw = [header sizeWithAttributes:attrs].width + kCellHPadding * 2;
  260. CGFloat vw = [value sizeWithAttributes:attrs].width + kCellHPadding * 2;
  261. CGFloat w = MAX(MAX(hw, vw), kMinColumnWidth);
  262. [colWidths addObject:@(w)];
  263. }
  264. BOOL isLast = NO;
  265. CGFloat x = 0;
  266. for (NSUInteger i = 0; i < columns.count; i++) {
  267. CGFloat w = [colWidths[i] floatValue];
  268. NSDictionary *col = columns[i];
  269. isLast = (i == columns.count - 1);
  270. // header 行:画上、左(不画下,避免与 data 行上边框叠加)
  271. UILabel *hLabel = [self makeCellLabel:col[@"header"]
  272. font:font
  273. borderColor:borderColor
  274. frame:CGRectMake(x, 0, w, kTableRowHeight)
  275. drawTop:YES drawBottom:NO isLast:isLast];
  276. hLabel.backgroundColor = [UIColor secondarySystemBackgroundColor];
  277. [self.priceScrollView addSubview:hLabel];
  278. // data 行:画下、左(不画上,避免与 header 行下边框叠加)
  279. UILabel *vLabel = [self makeCellLabel:col[@"value"]
  280. font:font
  281. borderColor:borderColor
  282. frame:CGRectMake(x, kTableRowHeight, w, kTableRowHeight)
  283. drawTop:NO drawBottom:YES isLast:isLast];
  284. [self.priceScrollView addSubview:vLabel];
  285. x += w;
  286. totalWidth = x;
  287. }
  288. self.priceScrollView.contentSize = CGSizeMake(totalWidth, kTableRowHeight * 2);
  289. }
  290. // MARK: - qtyField 数字过滤(iPad 全键盘模式)
  291. - (void)qtyFieldDidChange:(UITextField *)textField {
  292. NSString *filtered = [[textField.text componentsSeparatedByCharactersInSet:
  293. [[NSCharacterSet decimalDigitCharacterSet] invertedSet]]
  294. componentsJoinedByString:@""];
  295. if (![textField.text isEqualToString:filtered])
  296. textField.text = filtered;
  297. }
  298. // MARK: - 辅助
  299. /**
  300. 用 CAShapeLayer 画边框,避免相邻列边框叠加变粗:
  301. 每列只画上、下、左三边;最后一列额外画右边。
  302. */
  303. - (UILabel *)makeCellLabel:(NSString *)text
  304. font:(UIFont *)font
  305. borderColor:(UIColor *)borderColor
  306. frame:(CGRect)frame
  307. drawTop:(BOOL)drawTop
  308. drawBottom:(BOOL)drawBottom
  309. isLast:(BOOL)isLast {
  310. UILabel *label = [[UILabel alloc] initWithFrame:frame];
  311. label.text = text ?: @"";
  312. label.font = font;
  313. label.textAlignment = NSTextAlignmentCenter;
  314. label.clipsToBounds = YES;
  315. CGFloat w = frame.size.width;
  316. CGFloat h = frame.size.height;
  317. CAShapeLayer *border = [CAShapeLayer layer];
  318. UIBezierPath *path = [UIBezierPath bezierPath];
  319. if (drawTop) {
  320. [path moveToPoint:CGPointMake(0, 0)];
  321. [path addLineToPoint:CGPointMake(w, 0)];
  322. }
  323. if (drawBottom) {
  324. [path moveToPoint:CGPointMake(0, h)];
  325. [path addLineToPoint:CGPointMake(w, h)];
  326. }
  327. // 左边(每列都画)
  328. [path moveToPoint:CGPointMake(0, 0)];
  329. [path addLineToPoint:CGPointMake(0, h)];
  330. // 最后一列补右边
  331. if (isLast) {
  332. [path moveToPoint:CGPointMake(w, 0)];
  333. [path addLineToPoint:CGPointMake(w, h)];
  334. }
  335. border.path = path.CGPath;
  336. border.strokeColor = borderColor.CGColor;
  337. border.lineWidth = 1.0;
  338. border.fillColor = [UIColor clearColor].CGColor;
  339. [label.layer addSublayer:border];
  340. return label;
  341. }
  342. @end